From 4ab4a0a162b7a6887bf6099b4612ce685537f735 Mon Sep 17 00:00:00 2001 From: Andrei Mihu Date: Wed, 2 May 2018 12:34:45 +0100 Subject: [PATCH] Update groups to support super admin authority level. (#198) --- api/api.pb.go | 1455 +++++++++++++---- api/api.pb.gw.go | 600 ++++++- api/api.proto | 241 ++- api/api.swagger.json | 497 +++++- console/console.pb.go | 118 +- console/console.proto | 8 +- console/console.swagger.json | 86 +- main.go | 6 +- migrate/migrate-packr.go | 4 +- migrate/sql/20180103142001_initial_schema.sql | 36 + server/api_group.go | 270 ++- server/api_link.go | 6 +- server/api_unlink.go | 4 +- server/console_gdpr.go | 10 +- server/core_authenticate.go | 13 +- server/core_channel.go | 54 + server/core_friend.go | 59 +- server/core_group.go | 956 +++++++++++ server/core_notification.go | 2 +- server/core_storage.go | 32 +- server/core_wallet.go | 21 +- server/db.go | 6 + server/logger.go | 41 +- server/runtime_nakama_module.go | 305 +++- tests/runtime_test.go | 74 +- tests/util.go | 6 +- 26 files changed, 4293 insertions(+), 617 deletions(-) create mode 100644 server/core_group.go diff --git a/api/api.pb.go b/api/api.pb.go index e8c5cd8f9..f1d3fe751 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -17,6 +17,7 @@ It has these top-level messages: AccountGoogle AccountSteam AddFriendsRequest + AddGroupUsersRequest AuthenticateCustomRequest AuthenticateDeviceRequest AuthenticateEmailRequest @@ -27,8 +28,9 @@ It has these top-level messages: BlockFriendsRequest ChannelMessage ChannelMessageList - CreateGroupsRequest + CreateGroupRequest DeleteFriendsRequest + DeleteGroupRequest DeleteLeaderboardRecordRequest DeleteNotificationsRequest DeleteStorageObjectId @@ -37,20 +39,26 @@ It has these top-level messages: Friends GetUsersRequest Group - Groups + GroupUserList ImportFacebookFriendsRequest + JoinGroupRequest + KickGroupUsersRequest LeaderboardRecord LeaderboardRecordList + LeaveGroupRequest LinkFacebookRequest ListChannelMessagesRequest + ListGroupUsersRequest ListLeaderboardRecordsRequest ListMatchesRequest ListNotificationsRequest ListStorageObjectsRequest + ListUserGroupsRequest Match MatchList Notification NotificationList + PromoteGroupUsersRequest ReadStorageObjectId ReadStorageObjectsRequest Rpc @@ -61,7 +69,9 @@ It has these top-level messages: StorageObjects StorageObjectList UpdateAccountRequest + UpdateGroupRequest User + UserGroupList Users WriteLeaderboardRecordRequest WriteStorageObject @@ -128,7 +138,83 @@ var Friend_State_value = map[string]int32{ func (x Friend_State) String() string { return proto.EnumName(Friend_State_name, int32(x)) } -func (Friend_State) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{25, 0} } +func (Friend_State) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{27, 0} } + +// The group role status. +type GroupUserList_GroupUser_State int32 + +const ( + // Default case. Assumed as SUPERADMIN state. + GroupUserList_GroupUser_STATE_UNSPECIFIED GroupUserList_GroupUser_State = 0 + // The user is a superadmin with full control of the group. + GroupUserList_GroupUser_SUPERADMIN GroupUserList_GroupUser_State = 1 + // The user is an admin with additional privileges. + GroupUserList_GroupUser_ADMIN GroupUserList_GroupUser_State = 2 + // The user is a regular member. + GroupUserList_GroupUser_MEMBER GroupUserList_GroupUser_State = 3 + // The user has requested to join the group + GroupUserList_GroupUser_JOIN_REQUEST GroupUserList_GroupUser_State = 4 +) + +var GroupUserList_GroupUser_State_name = map[int32]string{ + 0: "STATE_UNSPECIFIED", + 1: "SUPERADMIN", + 2: "ADMIN", + 3: "MEMBER", + 4: "JOIN_REQUEST", +} +var GroupUserList_GroupUser_State_value = map[string]int32{ + "STATE_UNSPECIFIED": 0, + "SUPERADMIN": 1, + "ADMIN": 2, + "MEMBER": 3, + "JOIN_REQUEST": 4, +} + +func (x GroupUserList_GroupUser_State) String() string { + return proto.EnumName(GroupUserList_GroupUser_State_name, int32(x)) +} +func (GroupUserList_GroupUser_State) EnumDescriptor() ([]byte, []int) { + return fileDescriptor0, []int{31, 0, 0} +} + +// The group role status. +type UserGroupList_UserGroup_State int32 + +const ( + // Default case. Assumed as SUPERADMIN state. + UserGroupList_UserGroup_STATE_UNSPECIFIED UserGroupList_UserGroup_State = 0 + // The user is a superadmin with full control of the group. + UserGroupList_UserGroup_SUPERADMIN UserGroupList_UserGroup_State = 1 + // The user is an admin with additional privileges. + UserGroupList_UserGroup_ADMIN UserGroupList_UserGroup_State = 2 + // The user is a regular member. + UserGroupList_UserGroup_MEMBER UserGroupList_UserGroup_State = 3 + // The user has requested to join the group + UserGroupList_UserGroup_JOIN_REQUEST UserGroupList_UserGroup_State = 4 +) + +var UserGroupList_UserGroup_State_name = map[int32]string{ + 0: "STATE_UNSPECIFIED", + 1: "SUPERADMIN", + 2: "ADMIN", + 3: "MEMBER", + 4: "JOIN_REQUEST", +} +var UserGroupList_UserGroup_State_value = map[string]int32{ + "STATE_UNSPECIFIED": 0, + "SUPERADMIN": 1, + "ADMIN": 2, + "MEMBER": 3, + "JOIN_REQUEST": 4, +} + +func (x UserGroupList_UserGroup_State) String() string { + return proto.EnumName(UserGroupList_UserGroup_State_name, int32(x)) +} +func (UserGroupList_UserGroup_State) EnumDescriptor() ([]byte, []int) { + return fileDescriptor0, []int{63, 0, 0} +} // A user with additional account details. Always the current user. type Account struct { @@ -400,6 +486,33 @@ func (m *AddFriendsRequest) GetUsernames() []string { return nil } +// Add users to a group. +type AddGroupUsersRequest struct { + // The group to add users to. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` + // The users to add. + UserIds []string `protobuf:"bytes,2,rep,name=user_ids,json=userIds" json:"user_ids,omitempty"` +} + +func (m *AddGroupUsersRequest) Reset() { *m = AddGroupUsersRequest{} } +func (m *AddGroupUsersRequest) String() string { return proto.CompactTextString(m) } +func (*AddGroupUsersRequest) ProtoMessage() {} +func (*AddGroupUsersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +func (m *AddGroupUsersRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + +func (m *AddGroupUsersRequest) GetUserIds() []string { + if m != nil { + return m.UserIds + } + return nil +} + // Authenticate against the server with a custom ID. type AuthenticateCustomRequest struct { // The custom account details. @@ -413,7 +526,7 @@ type AuthenticateCustomRequest struct { func (m *AuthenticateCustomRequest) Reset() { *m = AuthenticateCustomRequest{} } func (m *AuthenticateCustomRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateCustomRequest) ProtoMessage() {} -func (*AuthenticateCustomRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } +func (*AuthenticateCustomRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } func (m *AuthenticateCustomRequest) GetAccount() *AccountCustom { if m != nil { @@ -449,7 +562,7 @@ type AuthenticateDeviceRequest struct { func (m *AuthenticateDeviceRequest) Reset() { *m = AuthenticateDeviceRequest{} } func (m *AuthenticateDeviceRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateDeviceRequest) ProtoMessage() {} -func (*AuthenticateDeviceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } +func (*AuthenticateDeviceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } func (m *AuthenticateDeviceRequest) GetAccount() *AccountDevice { if m != nil { @@ -485,7 +598,7 @@ type AuthenticateEmailRequest struct { func (m *AuthenticateEmailRequest) Reset() { *m = AuthenticateEmailRequest{} } func (m *AuthenticateEmailRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateEmailRequest) ProtoMessage() {} -func (*AuthenticateEmailRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } +func (*AuthenticateEmailRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } func (m *AuthenticateEmailRequest) GetAccount() *AccountEmail { if m != nil { @@ -523,7 +636,7 @@ type AuthenticateFacebookRequest struct { func (m *AuthenticateFacebookRequest) Reset() { *m = AuthenticateFacebookRequest{} } func (m *AuthenticateFacebookRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateFacebookRequest) ProtoMessage() {} -func (*AuthenticateFacebookRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } +func (*AuthenticateFacebookRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } func (m *AuthenticateFacebookRequest) GetAccount() *AccountFacebook { if m != nil { @@ -566,7 +679,7 @@ type AuthenticateGameCenterRequest struct { func (m *AuthenticateGameCenterRequest) Reset() { *m = AuthenticateGameCenterRequest{} } func (m *AuthenticateGameCenterRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateGameCenterRequest) ProtoMessage() {} -func (*AuthenticateGameCenterRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } +func (*AuthenticateGameCenterRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } func (m *AuthenticateGameCenterRequest) GetAccount() *AccountGameCenter { if m != nil { @@ -602,7 +715,7 @@ type AuthenticateGoogleRequest struct { func (m *AuthenticateGoogleRequest) Reset() { *m = AuthenticateGoogleRequest{} } func (m *AuthenticateGoogleRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateGoogleRequest) ProtoMessage() {} -func (*AuthenticateGoogleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } +func (*AuthenticateGoogleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } func (m *AuthenticateGoogleRequest) GetAccount() *AccountGoogle { if m != nil { @@ -638,7 +751,7 @@ type AuthenticateSteamRequest struct { func (m *AuthenticateSteamRequest) Reset() { *m = AuthenticateSteamRequest{} } func (m *AuthenticateSteamRequest) String() string { return proto.CompactTextString(m) } func (*AuthenticateSteamRequest) ProtoMessage() {} -func (*AuthenticateSteamRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } +func (*AuthenticateSteamRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } func (m *AuthenticateSteamRequest) GetAccount() *AccountSteam { if m != nil { @@ -672,7 +785,7 @@ type BlockFriendsRequest struct { func (m *BlockFriendsRequest) Reset() { *m = BlockFriendsRequest{} } func (m *BlockFriendsRequest) String() string { return proto.CompactTextString(m) } func (*BlockFriendsRequest) ProtoMessage() {} -func (*BlockFriendsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } +func (*BlockFriendsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } func (m *BlockFriendsRequest) GetIds() []string { if m != nil { @@ -713,7 +826,7 @@ type ChannelMessage struct { func (m *ChannelMessage) Reset() { *m = ChannelMessage{} } func (m *ChannelMessage) String() string { return proto.CompactTextString(m) } func (*ChannelMessage) ProtoMessage() {} -func (*ChannelMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } +func (*ChannelMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } func (m *ChannelMessage) GetChannelId() string { if m != nil { @@ -791,7 +904,7 @@ type ChannelMessageList struct { func (m *ChannelMessageList) Reset() { *m = ChannelMessageList{} } func (m *ChannelMessageList) String() string { return proto.CompactTextString(m) } func (*ChannelMessageList) ProtoMessage() {} -func (*ChannelMessageList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } +func (*ChannelMessageList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } func (m *ChannelMessageList) GetMessages() []*ChannelMessage { if m != nil { @@ -814,85 +927,56 @@ func (m *ChannelMessageList) GetPrevCursor() string { return "" } -// Create one or more groups with the current user as owner. -type CreateGroupsRequest struct { - // The Group objects to create. - Groups []*CreateGroupsRequest_NewGroup `protobuf:"bytes,1,rep,name=groups" json:"groups,omitempty"` -} - -func (m *CreateGroupsRequest) Reset() { *m = CreateGroupsRequest{} } -func (m *CreateGroupsRequest) String() string { return proto.CompactTextString(m) } -func (*CreateGroupsRequest) ProtoMessage() {} -func (*CreateGroupsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } - -func (m *CreateGroupsRequest) GetGroups() []*CreateGroupsRequest_NewGroup { - if m != nil { - return m.Groups - } - return nil -} - -// A group to create. -type CreateGroupsRequest_NewGroup struct { +// Create a group with the current user as owner. +type CreateGroupRequest struct { // A unique name for the group. Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // A description for the group. Description string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"` // The language expected to be a tag which follows the BCP-47 spec. LangTag string `protobuf:"bytes,3,opt,name=lang_tag,json=langTag" json:"lang_tag,omitempty"` - // Additional information stored as a JSON object. - Metadata string `protobuf:"bytes,4,opt,name=metadata" json:"metadata,omitempty"` // A URL for an avatar image. - AvatarUrl string `protobuf:"bytes,5,opt,name=avatar_url,json=avatarUrl" json:"avatar_url,omitempty"` - // Mark a group as private where only admins can accept members. - Private bool `protobuf:"varint,6,opt,name=private" json:"private,omitempty"` + AvatarUrl string `protobuf:"bytes,4,opt,name=avatar_url,json=avatarUrl" json:"avatar_url,omitempty"` + // Mark a group as open or not where only admins can accept members. + Open bool `protobuf:"varint,5,opt,name=open" json:"open,omitempty"` } -func (m *CreateGroupsRequest_NewGroup) Reset() { *m = CreateGroupsRequest_NewGroup{} } -func (m *CreateGroupsRequest_NewGroup) String() string { return proto.CompactTextString(m) } -func (*CreateGroupsRequest_NewGroup) ProtoMessage() {} -func (*CreateGroupsRequest_NewGroup) Descriptor() ([]byte, []int) { - return fileDescriptor0, []int{19, 0} -} +func (m *CreateGroupRequest) Reset() { *m = CreateGroupRequest{} } +func (m *CreateGroupRequest) String() string { return proto.CompactTextString(m) } +func (*CreateGroupRequest) ProtoMessage() {} +func (*CreateGroupRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } -func (m *CreateGroupsRequest_NewGroup) GetName() string { +func (m *CreateGroupRequest) GetName() string { if m != nil { return m.Name } return "" } -func (m *CreateGroupsRequest_NewGroup) GetDescription() string { +func (m *CreateGroupRequest) GetDescription() string { if m != nil { return m.Description } return "" } -func (m *CreateGroupsRequest_NewGroup) GetLangTag() string { +func (m *CreateGroupRequest) GetLangTag() string { if m != nil { return m.LangTag } return "" } -func (m *CreateGroupsRequest_NewGroup) GetMetadata() string { - if m != nil { - return m.Metadata - } - return "" -} - -func (m *CreateGroupsRequest_NewGroup) GetAvatarUrl() string { +func (m *CreateGroupRequest) GetAvatarUrl() string { if m != nil { return m.AvatarUrl } return "" } -func (m *CreateGroupsRequest_NewGroup) GetPrivate() bool { +func (m *CreateGroupRequest) GetOpen() bool { if m != nil { - return m.Private + return m.Open } return false } @@ -908,7 +992,7 @@ type DeleteFriendsRequest struct { func (m *DeleteFriendsRequest) Reset() { *m = DeleteFriendsRequest{} } func (m *DeleteFriendsRequest) String() string { return proto.CompactTextString(m) } func (*DeleteFriendsRequest) ProtoMessage() {} -func (*DeleteFriendsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } +func (*DeleteFriendsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } func (m *DeleteFriendsRequest) GetIds() []string { if m != nil { @@ -924,6 +1008,24 @@ func (m *DeleteFriendsRequest) GetUsernames() []string { return nil } +// Delete a group the user has access to. +type DeleteGroupRequest struct { + // The id of a group. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` +} + +func (m *DeleteGroupRequest) Reset() { *m = DeleteGroupRequest{} } +func (m *DeleteGroupRequest) String() string { return proto.CompactTextString(m) } +func (*DeleteGroupRequest) ProtoMessage() {} +func (*DeleteGroupRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } + +func (m *DeleteGroupRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + // Delete a leaderboard record. type DeleteLeaderboardRecordRequest struct { // The leaderboard ID to delete from. @@ -933,7 +1035,7 @@ type DeleteLeaderboardRecordRequest struct { func (m *DeleteLeaderboardRecordRequest) Reset() { *m = DeleteLeaderboardRecordRequest{} } func (m *DeleteLeaderboardRecordRequest) String() string { return proto.CompactTextString(m) } func (*DeleteLeaderboardRecordRequest) ProtoMessage() {} -func (*DeleteLeaderboardRecordRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } +func (*DeleteLeaderboardRecordRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} } func (m *DeleteLeaderboardRecordRequest) GetLeaderboardId() string { if m != nil { @@ -951,7 +1053,7 @@ type DeleteNotificationsRequest struct { func (m *DeleteNotificationsRequest) Reset() { *m = DeleteNotificationsRequest{} } func (m *DeleteNotificationsRequest) String() string { return proto.CompactTextString(m) } func (*DeleteNotificationsRequest) ProtoMessage() {} -func (*DeleteNotificationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } +func (*DeleteNotificationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} } func (m *DeleteNotificationsRequest) GetIds() []string { if m != nil { @@ -973,7 +1075,7 @@ type DeleteStorageObjectId struct { func (m *DeleteStorageObjectId) Reset() { *m = DeleteStorageObjectId{} } func (m *DeleteStorageObjectId) String() string { return proto.CompactTextString(m) } func (*DeleteStorageObjectId) ProtoMessage() {} -func (*DeleteStorageObjectId) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} } +func (*DeleteStorageObjectId) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} } func (m *DeleteStorageObjectId) GetCollection() string { if m != nil { @@ -1005,7 +1107,7 @@ type DeleteStorageObjectsRequest struct { func (m *DeleteStorageObjectsRequest) Reset() { *m = DeleteStorageObjectsRequest{} } func (m *DeleteStorageObjectsRequest) String() string { return proto.CompactTextString(m) } func (*DeleteStorageObjectsRequest) ProtoMessage() {} -func (*DeleteStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} } +func (*DeleteStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} } func (m *DeleteStorageObjectsRequest) GetObjectIds() []*DeleteStorageObjectId { if m != nil { @@ -1025,7 +1127,7 @@ type Friend struct { func (m *Friend) Reset() { *m = Friend{} } func (m *Friend) String() string { return proto.CompactTextString(m) } func (*Friend) ProtoMessage() {} -func (*Friend) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} } +func (*Friend) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} } func (m *Friend) GetUser() *User { if m != nil { @@ -1050,7 +1152,7 @@ type Friends struct { func (m *Friends) Reset() { *m = Friends{} } func (m *Friends) String() string { return proto.CompactTextString(m) } func (*Friends) ProtoMessage() {} -func (*Friends) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} } +func (*Friends) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{28} } func (m *Friends) GetFriends() []*Friend { if m != nil { @@ -1072,7 +1174,7 @@ type GetUsersRequest struct { func (m *GetUsersRequest) Reset() { *m = GetUsersRequest{} } func (m *GetUsersRequest) String() string { return proto.CompactTextString(m) } func (*GetUsersRequest) ProtoMessage() {} -func (*GetUsersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} } +func (*GetUsersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{29} } func (m *GetUsersRequest) GetIds() []string { if m != nil { @@ -1111,20 +1213,22 @@ type Group struct { Metadata string `protobuf:"bytes,6,opt,name=metadata" json:"metadata,omitempty"` // A URL for an avatar image. AvatarUrl string `protobuf:"bytes,7,opt,name=avatar_url,json=avatarUrl" json:"avatar_url,omitempty"` - // Mark a group as private where only admins can accept members. - Private bool `protobuf:"varint,8,opt,name=private" json:"private,omitempty"` + // Anyone can join open groups, otherwise only admins can accept members. + Open *google_protobuf3.BoolValue `protobuf:"bytes,8,opt,name=open" json:"open,omitempty"` // The current count of all members in the group. - Count int32 `protobuf:"varint,9,opt,name=count" json:"count,omitempty"` + EdgeCount int32 `protobuf:"varint,9,opt,name=edge_count,json=edgeCount" json:"edge_count,omitempty"` + // The maximum number of members allowed. + MaxCount int32 `protobuf:"varint,10,opt,name=max_count,json=maxCount" json:"max_count,omitempty"` // The UNIX time when the group was created. - CreateTime *google_protobuf2.Timestamp `protobuf:"bytes,10,opt,name=create_time,json=createTime" json:"create_time,omitempty"` + CreateTime *google_protobuf2.Timestamp `protobuf:"bytes,11,opt,name=create_time,json=createTime" json:"create_time,omitempty"` // The UNIX time when the group was last updated. - UpdateTime *google_protobuf2.Timestamp `protobuf:"bytes,11,opt,name=update_time,json=updateTime" json:"update_time,omitempty"` + UpdateTime *google_protobuf2.Timestamp `protobuf:"bytes,12,opt,name=update_time,json=updateTime" json:"update_time,omitempty"` } func (m *Group) Reset() { *m = Group{} } func (m *Group) String() string { return proto.CompactTextString(m) } func (*Group) ProtoMessage() {} -func (*Group) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{28} } +func (*Group) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{30} } func (m *Group) GetId() string { if m != nil { @@ -1175,16 +1279,23 @@ func (m *Group) GetAvatarUrl() string { return "" } -func (m *Group) GetPrivate() bool { +func (m *Group) GetOpen() *google_protobuf3.BoolValue { if m != nil { - return m.Private + return m.Open } - return false + return nil +} + +func (m *Group) GetEdgeCount() int32 { + if m != nil { + return m.EdgeCount + } + return 0 } -func (m *Group) GetCount() int32 { +func (m *Group) GetMaxCount() int32 { if m != nil { - return m.Count + return m.MaxCount } return 0 } @@ -1203,24 +1314,51 @@ func (m *Group) GetUpdateTime() *google_protobuf2.Timestamp { return nil } -// A collection of zero or more groups. -type Groups struct { - // The Group objects. - Groups []*Group `protobuf:"bytes,1,rep,name=groups" json:"groups,omitempty"` +// A list of users belonging to a group, along with their role. +type GroupUserList struct { + // User-role pairs for a group. + GroupUsers []*GroupUserList_GroupUser `protobuf:"bytes,1,rep,name=group_users,json=groupUsers" json:"group_users,omitempty"` } -func (m *Groups) Reset() { *m = Groups{} } -func (m *Groups) String() string { return proto.CompactTextString(m) } -func (*Groups) ProtoMessage() {} -func (*Groups) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{29} } +func (m *GroupUserList) Reset() { *m = GroupUserList{} } +func (m *GroupUserList) String() string { return proto.CompactTextString(m) } +func (*GroupUserList) ProtoMessage() {} +func (*GroupUserList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} } -func (m *Groups) GetGroups() []*Group { +func (m *GroupUserList) GetGroupUsers() []*GroupUserList_GroupUser { if m != nil { - return m.Groups + return m.GroupUsers } return nil } +// A single user-role pair. +type GroupUserList_GroupUser struct { + // User. + User *User `protobuf:"bytes,1,opt,name=user" json:"user,omitempty"` + // Their relationship to the group. + State int32 `protobuf:"varint,2,opt,name=state" json:"state,omitempty"` +} + +func (m *GroupUserList_GroupUser) Reset() { *m = GroupUserList_GroupUser{} } +func (m *GroupUserList_GroupUser) String() string { return proto.CompactTextString(m) } +func (*GroupUserList_GroupUser) ProtoMessage() {} +func (*GroupUserList_GroupUser) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31, 0} } + +func (m *GroupUserList_GroupUser) GetUser() *User { + if m != nil { + return m.User + } + return nil +} + +func (m *GroupUserList_GroupUser) GetState() int32 { + if m != nil { + return m.State + } + return 0 +} + // Import Facebook friends into the current user's account. type ImportFacebookFriendsRequest struct { // The Facebook account details. @@ -1232,7 +1370,7 @@ type ImportFacebookFriendsRequest struct { func (m *ImportFacebookFriendsRequest) Reset() { *m = ImportFacebookFriendsRequest{} } func (m *ImportFacebookFriendsRequest) String() string { return proto.CompactTextString(m) } func (*ImportFacebookFriendsRequest) ProtoMessage() {} -func (*ImportFacebookFriendsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{30} } +func (*ImportFacebookFriendsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} } func (m *ImportFacebookFriendsRequest) GetAccount() *AccountFacebook { if m != nil { @@ -1248,6 +1386,51 @@ func (m *ImportFacebookFriendsRequest) GetReset_() *google_protobuf3.BoolValue { return nil } +// Immediately join an open group, or request to join a closed one. +type JoinGroupRequest struct { + // The group ID to join. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` +} + +func (m *JoinGroupRequest) Reset() { *m = JoinGroupRequest{} } +func (m *JoinGroupRequest) String() string { return proto.CompactTextString(m) } +func (*JoinGroupRequest) ProtoMessage() {} +func (*JoinGroupRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{33} } + +func (m *JoinGroupRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + +// Kick a set of users from a group. +type KickGroupUsersRequest struct { + // The group ID to kick from. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` + // The users to kick. + UserIds []string `protobuf:"bytes,2,rep,name=user_ids,json=userIds" json:"user_ids,omitempty"` +} + +func (m *KickGroupUsersRequest) Reset() { *m = KickGroupUsersRequest{} } +func (m *KickGroupUsersRequest) String() string { return proto.CompactTextString(m) } +func (*KickGroupUsersRequest) ProtoMessage() {} +func (*KickGroupUsersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{34} } + +func (m *KickGroupUsersRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + +func (m *KickGroupUsersRequest) GetUserIds() []string { + if m != nil { + return m.UserIds + } + return nil +} + // Represents a complete leaderboard record with all scores and associated metadata. type LeaderboardRecord struct { // The ID of the leaderboard this score belongs to. @@ -1277,7 +1460,7 @@ type LeaderboardRecord struct { func (m *LeaderboardRecord) Reset() { *m = LeaderboardRecord{} } func (m *LeaderboardRecord) String() string { return proto.CompactTextString(m) } func (*LeaderboardRecord) ProtoMessage() {} -func (*LeaderboardRecord) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} } +func (*LeaderboardRecord) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{35} } func (m *LeaderboardRecord) GetLeaderboardId() string { if m != nil { @@ -1371,7 +1554,7 @@ type LeaderboardRecordList struct { func (m *LeaderboardRecordList) Reset() { *m = LeaderboardRecordList{} } func (m *LeaderboardRecordList) String() string { return proto.CompactTextString(m) } func (*LeaderboardRecordList) ProtoMessage() {} -func (*LeaderboardRecordList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} } +func (*LeaderboardRecordList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{36} } func (m *LeaderboardRecordList) GetRecords() []*LeaderboardRecord { if m != nil { @@ -1401,6 +1584,24 @@ func (m *LeaderboardRecordList) GetPrevCursor() string { return "" } +// Leave a group. +type LeaveGroupRequest struct { + // The group ID to leave. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` +} + +func (m *LeaveGroupRequest) Reset() { *m = LeaveGroupRequest{} } +func (m *LeaveGroupRequest) String() string { return proto.CompactTextString(m) } +func (*LeaveGroupRequest) ProtoMessage() {} +func (*LeaveGroupRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{37} } + +func (m *LeaveGroupRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + // Link Facebook to the current user's account. type LinkFacebookRequest struct { // The Facebook account details. @@ -1412,7 +1613,7 @@ type LinkFacebookRequest struct { func (m *LinkFacebookRequest) Reset() { *m = LinkFacebookRequest{} } func (m *LinkFacebookRequest) String() string { return proto.CompactTextString(m) } func (*LinkFacebookRequest) ProtoMessage() {} -func (*LinkFacebookRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{33} } +func (*LinkFacebookRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{38} } func (m *LinkFacebookRequest) GetAccount() *AccountFacebook { if m != nil { @@ -1443,7 +1644,7 @@ type ListChannelMessagesRequest struct { func (m *ListChannelMessagesRequest) Reset() { *m = ListChannelMessagesRequest{} } func (m *ListChannelMessagesRequest) String() string { return proto.CompactTextString(m) } func (*ListChannelMessagesRequest) ProtoMessage() {} -func (*ListChannelMessagesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{34} } +func (*ListChannelMessagesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{39} } func (m *ListChannelMessagesRequest) GetChannelId() string { if m != nil { @@ -1473,6 +1674,24 @@ func (m *ListChannelMessagesRequest) GetCursor() string { return "" } +// List all users that are part of a group. +type ListGroupUsersRequest struct { + // The group ID to list from. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` +} + +func (m *ListGroupUsersRequest) Reset() { *m = ListGroupUsersRequest{} } +func (m *ListGroupUsersRequest) String() string { return proto.CompactTextString(m) } +func (*ListGroupUsersRequest) ProtoMessage() {} +func (*ListGroupUsersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{40} } + +func (m *ListGroupUsersRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + // List leaderboard records from a given leaderboard. type ListLeaderboardRecordsRequest struct { // The ID of the leaderboard to list for. @@ -1488,7 +1707,7 @@ type ListLeaderboardRecordsRequest struct { func (m *ListLeaderboardRecordsRequest) Reset() { *m = ListLeaderboardRecordsRequest{} } func (m *ListLeaderboardRecordsRequest) String() string { return proto.CompactTextString(m) } func (*ListLeaderboardRecordsRequest) ProtoMessage() {} -func (*ListLeaderboardRecordsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{35} } +func (*ListLeaderboardRecordsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{41} } func (m *ListLeaderboardRecordsRequest) GetLeaderboardId() string { if m != nil { @@ -1535,7 +1754,7 @@ type ListMatchesRequest struct { func (m *ListMatchesRequest) Reset() { *m = ListMatchesRequest{} } func (m *ListMatchesRequest) String() string { return proto.CompactTextString(m) } func (*ListMatchesRequest) ProtoMessage() {} -func (*ListMatchesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{36} } +func (*ListMatchesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{42} } func (m *ListMatchesRequest) GetLimit() *google_protobuf3.Int32Value { if m != nil { @@ -1583,7 +1802,7 @@ type ListNotificationsRequest struct { func (m *ListNotificationsRequest) Reset() { *m = ListNotificationsRequest{} } func (m *ListNotificationsRequest) String() string { return proto.CompactTextString(m) } func (*ListNotificationsRequest) ProtoMessage() {} -func (*ListNotificationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{37} } +func (*ListNotificationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{43} } func (m *ListNotificationsRequest) GetLimit() *google_protobuf3.Int32Value { if m != nil { @@ -1614,7 +1833,7 @@ type ListStorageObjectsRequest struct { func (m *ListStorageObjectsRequest) Reset() { *m = ListStorageObjectsRequest{} } func (m *ListStorageObjectsRequest) String() string { return proto.CompactTextString(m) } func (*ListStorageObjectsRequest) ProtoMessage() {} -func (*ListStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{38} } +func (*ListStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{44} } func (m *ListStorageObjectsRequest) GetUserId() string { if m != nil { @@ -1644,6 +1863,24 @@ func (m *ListStorageObjectsRequest) GetCursor() string { return "" } +// List the groups a user is part of, and their relationship to each. +type ListUserGroupsRequest struct { + // ID of the user. + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId" json:"user_id,omitempty"` +} + +func (m *ListUserGroupsRequest) Reset() { *m = ListUserGroupsRequest{} } +func (m *ListUserGroupsRequest) String() string { return proto.CompactTextString(m) } +func (*ListUserGroupsRequest) ProtoMessage() {} +func (*ListUserGroupsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{45} } + +func (m *ListUserGroupsRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + // Represents a realtime match. type Match struct { // The ID of the match, can be used to join. @@ -1659,7 +1896,7 @@ type Match struct { func (m *Match) Reset() { *m = Match{} } func (m *Match) String() string { return proto.CompactTextString(m) } func (*Match) ProtoMessage() {} -func (*Match) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{39} } +func (*Match) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{46} } func (m *Match) GetMatchId() string { if m != nil { @@ -1698,7 +1935,7 @@ type MatchList struct { func (m *MatchList) Reset() { *m = MatchList{} } func (m *MatchList) String() string { return proto.CompactTextString(m) } func (*MatchList) ProtoMessage() {} -func (*MatchList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{40} } +func (*MatchList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} } func (m *MatchList) GetMatches() []*Match { if m != nil { @@ -1728,7 +1965,7 @@ type Notification struct { func (m *Notification) Reset() { *m = Notification{} } func (m *Notification) String() string { return proto.CompactTextString(m) } func (*Notification) ProtoMessage() {} -func (*Notification) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{41} } +func (*Notification) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{48} } func (m *Notification) GetId() string { if m != nil { @@ -1790,7 +2027,7 @@ type NotificationList struct { func (m *NotificationList) Reset() { *m = NotificationList{} } func (m *NotificationList) String() string { return proto.CompactTextString(m) } func (*NotificationList) ProtoMessage() {} -func (*NotificationList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{42} } +func (*NotificationList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{49} } func (m *NotificationList) GetNotifications() []*Notification { if m != nil { @@ -1806,6 +2043,33 @@ func (m *NotificationList) GetCacheableCursor() string { return "" } +// Promote a set of users in a group to the next role up. +type PromoteGroupUsersRequest struct { + // The group ID to promote in. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` + // The users to promote. + UserIds []string `protobuf:"bytes,2,rep,name=user_ids,json=userIds" json:"user_ids,omitempty"` +} + +func (m *PromoteGroupUsersRequest) Reset() { *m = PromoteGroupUsersRequest{} } +func (m *PromoteGroupUsersRequest) String() string { return proto.CompactTextString(m) } +func (*PromoteGroupUsersRequest) ProtoMessage() {} +func (*PromoteGroupUsersRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} } + +func (m *PromoteGroupUsersRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + +func (m *PromoteGroupUsersRequest) GetUserIds() []string { + if m != nil { + return m.UserIds + } + return nil +} + // Storage objects to get. type ReadStorageObjectId struct { // The collection which stores the object. @@ -1819,7 +2083,7 @@ type ReadStorageObjectId struct { func (m *ReadStorageObjectId) Reset() { *m = ReadStorageObjectId{} } func (m *ReadStorageObjectId) String() string { return proto.CompactTextString(m) } func (*ReadStorageObjectId) ProtoMessage() {} -func (*ReadStorageObjectId) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{43} } +func (*ReadStorageObjectId) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{51} } func (m *ReadStorageObjectId) GetCollection() string { if m != nil { @@ -1851,7 +2115,7 @@ type ReadStorageObjectsRequest struct { func (m *ReadStorageObjectsRequest) Reset() { *m = ReadStorageObjectsRequest{} } func (m *ReadStorageObjectsRequest) String() string { return proto.CompactTextString(m) } func (*ReadStorageObjectsRequest) ProtoMessage() {} -func (*ReadStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{44} } +func (*ReadStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{52} } func (m *ReadStorageObjectsRequest) GetObjectIds() []*ReadStorageObjectId { if m != nil { @@ -1873,7 +2137,7 @@ type Rpc struct { func (m *Rpc) Reset() { *m = Rpc{} } func (m *Rpc) String() string { return proto.CompactTextString(m) } func (*Rpc) ProtoMessage() {} -func (*Rpc) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{45} } +func (*Rpc) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{53} } func (m *Rpc) GetId() string { if m != nil { @@ -1909,7 +2173,7 @@ type Session struct { func (m *Session) Reset() { *m = Session{} } func (m *Session) String() string { return proto.CompactTextString(m) } func (*Session) ProtoMessage() {} -func (*Session) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{46} } +func (*Session) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{54} } func (m *Session) GetCreated() bool { if m != nil { @@ -1957,7 +2221,7 @@ type StorageObject struct { func (m *StorageObject) Reset() { *m = StorageObject{} } func (m *StorageObject) String() string { return proto.CompactTextString(m) } func (*StorageObject) ProtoMessage() {} -func (*StorageObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} } +func (*StorageObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{55} } func (m *StorageObject) GetCollection() string { if m != nil { @@ -2037,7 +2301,7 @@ type StorageObjectAck struct { func (m *StorageObjectAck) Reset() { *m = StorageObjectAck{} } func (m *StorageObjectAck) String() string { return proto.CompactTextString(m) } func (*StorageObjectAck) ProtoMessage() {} -func (*StorageObjectAck) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{48} } +func (*StorageObjectAck) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{56} } func (m *StorageObjectAck) GetCollection() string { if m != nil { @@ -2076,7 +2340,7 @@ type StorageObjectAcks struct { func (m *StorageObjectAcks) Reset() { *m = StorageObjectAcks{} } func (m *StorageObjectAcks) String() string { return proto.CompactTextString(m) } func (*StorageObjectAcks) ProtoMessage() {} -func (*StorageObjectAcks) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{49} } +func (*StorageObjectAcks) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{57} } func (m *StorageObjectAcks) GetAcks() []*StorageObjectAck { if m != nil { @@ -2094,7 +2358,7 @@ type StorageObjects struct { func (m *StorageObjects) Reset() { *m = StorageObjects{} } func (m *StorageObjects) String() string { return proto.CompactTextString(m) } func (*StorageObjects) ProtoMessage() {} -func (*StorageObjects) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} } +func (*StorageObjects) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{58} } func (m *StorageObjects) GetObjects() []*StorageObject { if m != nil { @@ -2114,7 +2378,7 @@ type StorageObjectList struct { func (m *StorageObjectList) Reset() { *m = StorageObjectList{} } func (m *StorageObjectList) String() string { return proto.CompactTextString(m) } func (*StorageObjectList) ProtoMessage() {} -func (*StorageObjectList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{51} } +func (*StorageObjectList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{59} } func (m *StorageObjectList) GetObjects() []*StorageObject { if m != nil { @@ -2149,7 +2413,7 @@ type UpdateAccountRequest struct { func (m *UpdateAccountRequest) Reset() { *m = UpdateAccountRequest{} } func (m *UpdateAccountRequest) String() string { return proto.CompactTextString(m) } func (*UpdateAccountRequest) ProtoMessage() {} -func (*UpdateAccountRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{52} } +func (*UpdateAccountRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{60} } func (m *UpdateAccountRequest) GetUsername() *google_protobuf3.StringValue { if m != nil { @@ -2193,6 +2457,69 @@ func (m *UpdateAccountRequest) GetTimezone() *google_protobuf3.StringValue { return nil } +// Update fields in a given group. +type UpdateGroupRequest struct { + // The ID of the group to update. + GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"` + // Name. + Name *google_protobuf3.StringValue `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` + // Description string. + Description *google_protobuf3.StringValue `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"` + // Lang tag. + LangTag *google_protobuf3.StringValue `protobuf:"bytes,4,opt,name=lang_tag,json=langTag" json:"lang_tag,omitempty"` + // Avatar URL. + AvatarUrl *google_protobuf3.StringValue `protobuf:"bytes,5,opt,name=avatar_url,json=avatarUrl" json:"avatar_url,omitempty"` + // Open is true if anyone should be allowed to join, or false if joins must be approved by a group admin. + Open *google_protobuf3.BoolValue `protobuf:"bytes,6,opt,name=open" json:"open,omitempty"` +} + +func (m *UpdateGroupRequest) Reset() { *m = UpdateGroupRequest{} } +func (m *UpdateGroupRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupRequest) ProtoMessage() {} +func (*UpdateGroupRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{61} } + +func (m *UpdateGroupRequest) GetGroupId() string { + if m != nil { + return m.GroupId + } + return "" +} + +func (m *UpdateGroupRequest) GetName() *google_protobuf3.StringValue { + if m != nil { + return m.Name + } + return nil +} + +func (m *UpdateGroupRequest) GetDescription() *google_protobuf3.StringValue { + if m != nil { + return m.Description + } + return nil +} + +func (m *UpdateGroupRequest) GetLangTag() *google_protobuf3.StringValue { + if m != nil { + return m.LangTag + } + return nil +} + +func (m *UpdateGroupRequest) GetAvatarUrl() *google_protobuf3.StringValue { + if m != nil { + return m.AvatarUrl + } + return nil +} + +func (m *UpdateGroupRequest) GetOpen() *google_protobuf3.BoolValue { + if m != nil { + return m.Open + } + return nil +} + // A user in the server. type User struct { // The id of the user's account. @@ -2232,7 +2559,7 @@ type User struct { func (m *User) Reset() { *m = User{} } func (m *User) String() string { return proto.CompactTextString(m) } func (*User) ProtoMessage() {} -func (*User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{53} } +func (*User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{62} } func (m *User) GetId() string { if m != nil { @@ -2346,6 +2673,51 @@ func (m *User) GetUpdateTime() *google_protobuf2.Timestamp { return nil } +// A list of groups belonging to a user, along with the user's role in each group. +type UserGroupList struct { + // Group-role pairs for a user. + UserGroups []*UserGroupList_UserGroup `protobuf:"bytes,1,rep,name=user_groups,json=userGroups" json:"user_groups,omitempty"` +} + +func (m *UserGroupList) Reset() { *m = UserGroupList{} } +func (m *UserGroupList) String() string { return proto.CompactTextString(m) } +func (*UserGroupList) ProtoMessage() {} +func (*UserGroupList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{63} } + +func (m *UserGroupList) GetUserGroups() []*UserGroupList_UserGroup { + if m != nil { + return m.UserGroups + } + return nil +} + +// A single group-role pair. +type UserGroupList_UserGroup struct { + // Group. + Group *Group `protobuf:"bytes,1,opt,name=group" json:"group,omitempty"` + // The user's relationship to the group. + State int32 `protobuf:"varint,2,opt,name=state" json:"state,omitempty"` +} + +func (m *UserGroupList_UserGroup) Reset() { *m = UserGroupList_UserGroup{} } +func (m *UserGroupList_UserGroup) String() string { return proto.CompactTextString(m) } +func (*UserGroupList_UserGroup) ProtoMessage() {} +func (*UserGroupList_UserGroup) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{63, 0} } + +func (m *UserGroupList_UserGroup) GetGroup() *Group { + if m != nil { + return m.Group + } + return nil +} + +func (m *UserGroupList_UserGroup) GetState() int32 { + if m != nil { + return m.State + } + return 0 +} + // A collection of zero or more users. type Users struct { // The User objects. @@ -2355,7 +2727,7 @@ type Users struct { func (m *Users) Reset() { *m = Users{} } func (m *Users) String() string { return proto.CompactTextString(m) } func (*Users) ProtoMessage() {} -func (*Users) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{54} } +func (*Users) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{64} } func (m *Users) GetUsers() []*User { if m != nil { @@ -2375,7 +2747,7 @@ type WriteLeaderboardRecordRequest struct { func (m *WriteLeaderboardRecordRequest) Reset() { *m = WriteLeaderboardRecordRequest{} } func (m *WriteLeaderboardRecordRequest) String() string { return proto.CompactTextString(m) } func (*WriteLeaderboardRecordRequest) ProtoMessage() {} -func (*WriteLeaderboardRecordRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{55} } +func (*WriteLeaderboardRecordRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{65} } func (m *WriteLeaderboardRecordRequest) GetLeaderboardId() string { if m != nil { @@ -2409,7 +2781,7 @@ func (m *WriteLeaderboardRecordRequest_LeaderboardRecordWrite) String() string { } func (*WriteLeaderboardRecordRequest_LeaderboardRecordWrite) ProtoMessage() {} func (*WriteLeaderboardRecordRequest_LeaderboardRecordWrite) Descriptor() ([]byte, []int) { - return fileDescriptor0, []int{55, 0} + return fileDescriptor0, []int{65, 0} } func (m *WriteLeaderboardRecordRequest_LeaderboardRecordWrite) GetScore() int64 { @@ -2452,7 +2824,7 @@ type WriteStorageObject struct { func (m *WriteStorageObject) Reset() { *m = WriteStorageObject{} } func (m *WriteStorageObject) String() string { return proto.CompactTextString(m) } func (*WriteStorageObject) ProtoMessage() {} -func (*WriteStorageObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{56} } +func (*WriteStorageObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{66} } func (m *WriteStorageObject) GetCollection() string { if m != nil { @@ -2505,7 +2877,7 @@ type WriteStorageObjectsRequest struct { func (m *WriteStorageObjectsRequest) Reset() { *m = WriteStorageObjectsRequest{} } func (m *WriteStorageObjectsRequest) String() string { return proto.CompactTextString(m) } func (*WriteStorageObjectsRequest) ProtoMessage() {} -func (*WriteStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{57} } +func (*WriteStorageObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{67} } func (m *WriteStorageObjectsRequest) GetObjects() []*WriteStorageObject { if m != nil { @@ -2524,6 +2896,7 @@ func init() { proto.RegisterType((*AccountGoogle)(nil), "nakama.api.AccountGoogle") proto.RegisterType((*AccountSteam)(nil), "nakama.api.AccountSteam") proto.RegisterType((*AddFriendsRequest)(nil), "nakama.api.AddFriendsRequest") + proto.RegisterType((*AddGroupUsersRequest)(nil), "nakama.api.AddGroupUsersRequest") proto.RegisterType((*AuthenticateCustomRequest)(nil), "nakama.api.AuthenticateCustomRequest") proto.RegisterType((*AuthenticateDeviceRequest)(nil), "nakama.api.AuthenticateDeviceRequest") proto.RegisterType((*AuthenticateEmailRequest)(nil), "nakama.api.AuthenticateEmailRequest") @@ -2534,9 +2907,9 @@ func init() { proto.RegisterType((*BlockFriendsRequest)(nil), "nakama.api.BlockFriendsRequest") proto.RegisterType((*ChannelMessage)(nil), "nakama.api.ChannelMessage") proto.RegisterType((*ChannelMessageList)(nil), "nakama.api.ChannelMessageList") - proto.RegisterType((*CreateGroupsRequest)(nil), "nakama.api.CreateGroupsRequest") - proto.RegisterType((*CreateGroupsRequest_NewGroup)(nil), "nakama.api.CreateGroupsRequest.NewGroup") + proto.RegisterType((*CreateGroupRequest)(nil), "nakama.api.CreateGroupRequest") proto.RegisterType((*DeleteFriendsRequest)(nil), "nakama.api.DeleteFriendsRequest") + proto.RegisterType((*DeleteGroupRequest)(nil), "nakama.api.DeleteGroupRequest") proto.RegisterType((*DeleteLeaderboardRecordRequest)(nil), "nakama.api.DeleteLeaderboardRecordRequest") proto.RegisterType((*DeleteNotificationsRequest)(nil), "nakama.api.DeleteNotificationsRequest") proto.RegisterType((*DeleteStorageObjectId)(nil), "nakama.api.DeleteStorageObjectId") @@ -2545,20 +2918,27 @@ func init() { proto.RegisterType((*Friends)(nil), "nakama.api.Friends") proto.RegisterType((*GetUsersRequest)(nil), "nakama.api.GetUsersRequest") proto.RegisterType((*Group)(nil), "nakama.api.Group") - proto.RegisterType((*Groups)(nil), "nakama.api.Groups") + proto.RegisterType((*GroupUserList)(nil), "nakama.api.GroupUserList") + proto.RegisterType((*GroupUserList_GroupUser)(nil), "nakama.api.GroupUserList.GroupUser") proto.RegisterType((*ImportFacebookFriendsRequest)(nil), "nakama.api.ImportFacebookFriendsRequest") + proto.RegisterType((*JoinGroupRequest)(nil), "nakama.api.JoinGroupRequest") + proto.RegisterType((*KickGroupUsersRequest)(nil), "nakama.api.KickGroupUsersRequest") proto.RegisterType((*LeaderboardRecord)(nil), "nakama.api.LeaderboardRecord") proto.RegisterType((*LeaderboardRecordList)(nil), "nakama.api.LeaderboardRecordList") + proto.RegisterType((*LeaveGroupRequest)(nil), "nakama.api.LeaveGroupRequest") proto.RegisterType((*LinkFacebookRequest)(nil), "nakama.api.LinkFacebookRequest") proto.RegisterType((*ListChannelMessagesRequest)(nil), "nakama.api.ListChannelMessagesRequest") + proto.RegisterType((*ListGroupUsersRequest)(nil), "nakama.api.ListGroupUsersRequest") proto.RegisterType((*ListLeaderboardRecordsRequest)(nil), "nakama.api.ListLeaderboardRecordsRequest") proto.RegisterType((*ListMatchesRequest)(nil), "nakama.api.ListMatchesRequest") proto.RegisterType((*ListNotificationsRequest)(nil), "nakama.api.ListNotificationsRequest") proto.RegisterType((*ListStorageObjectsRequest)(nil), "nakama.api.ListStorageObjectsRequest") + proto.RegisterType((*ListUserGroupsRequest)(nil), "nakama.api.ListUserGroupsRequest") proto.RegisterType((*Match)(nil), "nakama.api.Match") proto.RegisterType((*MatchList)(nil), "nakama.api.MatchList") proto.RegisterType((*Notification)(nil), "nakama.api.Notification") proto.RegisterType((*NotificationList)(nil), "nakama.api.NotificationList") + proto.RegisterType((*PromoteGroupUsersRequest)(nil), "nakama.api.PromoteGroupUsersRequest") proto.RegisterType((*ReadStorageObjectId)(nil), "nakama.api.ReadStorageObjectId") proto.RegisterType((*ReadStorageObjectsRequest)(nil), "nakama.api.ReadStorageObjectsRequest") proto.RegisterType((*Rpc)(nil), "nakama.api.Rpc") @@ -2569,13 +2949,18 @@ func init() { proto.RegisterType((*StorageObjects)(nil), "nakama.api.StorageObjects") proto.RegisterType((*StorageObjectList)(nil), "nakama.api.StorageObjectList") proto.RegisterType((*UpdateAccountRequest)(nil), "nakama.api.UpdateAccountRequest") + proto.RegisterType((*UpdateGroupRequest)(nil), "nakama.api.UpdateGroupRequest") proto.RegisterType((*User)(nil), "nakama.api.User") + proto.RegisterType((*UserGroupList)(nil), "nakama.api.UserGroupList") + proto.RegisterType((*UserGroupList_UserGroup)(nil), "nakama.api.UserGroupList.UserGroup") proto.RegisterType((*Users)(nil), "nakama.api.Users") proto.RegisterType((*WriteLeaderboardRecordRequest)(nil), "nakama.api.WriteLeaderboardRecordRequest") proto.RegisterType((*WriteLeaderboardRecordRequest_LeaderboardRecordWrite)(nil), "nakama.api.WriteLeaderboardRecordRequest.LeaderboardRecordWrite") proto.RegisterType((*WriteStorageObject)(nil), "nakama.api.WriteStorageObject") proto.RegisterType((*WriteStorageObjectsRequest)(nil), "nakama.api.WriteStorageObjectsRequest") proto.RegisterEnum("nakama.api.Friend_State", Friend_State_name, Friend_State_value) + proto.RegisterEnum("nakama.api.GroupUserList_GroupUser_State", GroupUserList_GroupUser_State_name, GroupUserList_GroupUser_State_value) + proto.RegisterEnum("nakama.api.UserGroupList_UserGroup_State", UserGroupList_UserGroup_State_name, UserGroupList_UserGroup_State_value) } // Reference imports to suppress errors if they are not otherwise used. @@ -2591,6 +2976,8 @@ const _ = grpc.SupportPackageIsVersion4 type NakamaClient interface { // Add friends by ID or username to a user's account. AddFriends(ctx context.Context, in *AddFriendsRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // Add users to a group. + AddGroupUsers(ctx context.Context, in *AddGroupUsersRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Authenticate a user with a custom id against the server. AuthenticateCustom(ctx context.Context, in *AuthenticateCustomRequest, opts ...grpc.CallOption) (*Session, error) // Authenticate a user with a device id against the server. @@ -2607,10 +2994,12 @@ type NakamaClient interface { AuthenticateSteam(ctx context.Context, in *AuthenticateSteamRequest, opts ...grpc.CallOption) (*Session, error) // Block one or more users by ID or username. BlockFriends(ctx context.Context, in *BlockFriendsRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) - // Create one or more new groups with the current user as the owner. - CreateGroup(ctx context.Context, in *CreateGroupsRequest, opts ...grpc.CallOption) (*Groups, error) + // Create a new group with the current user as the owner. + CreateGroup(ctx context.Context, in *CreateGroupRequest, opts ...grpc.CallOption) (*Group, error) // Delete one or more users by ID or username. DeleteFriends(ctx context.Context, in *DeleteFriendsRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // Delete one or more groups by ID. + DeleteGroup(ctx context.Context, in *DeleteGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Delete a leaderboard record. DeleteLeaderboardRecord(ctx context.Context, in *DeleteLeaderboardRecordRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Delete one or more users by ID or username. @@ -2625,6 +3014,12 @@ type NakamaClient interface { Healthcheck(ctx context.Context, in *google_protobuf1.Empty, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Import Facebook friends and add them to a user's account. ImportFacebookFriends(ctx context.Context, in *ImportFacebookFriendsRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // Immediately join an open group, or request to join a closed one. + JoinGroup(ctx context.Context, in *JoinGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // Kick a set of users from a group. + KickGroupUsers(ctx context.Context, in *KickGroupUsersRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // Leave a group the user is a member of. + LeaveGroup(ctx context.Context, in *LeaveGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Add a custom ID to the social profiles on the current user's account. LinkCustom(ctx context.Context, in *AccountCustom, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Add a device ID to the social profiles on the current user's account. @@ -2643,6 +3038,8 @@ type NakamaClient interface { ListChannelMessages(ctx context.Context, in *ListChannelMessagesRequest, opts ...grpc.CallOption) (*ChannelMessageList, error) // List all friends for the current user. ListFriends(ctx context.Context, in *google_protobuf1.Empty, opts ...grpc.CallOption) (*Friends, error) + // List all users that are part of a group. + ListGroupUsers(ctx context.Context, in *ListGroupUsersRequest, opts ...grpc.CallOption) (*GroupUserList, error) // List leaderboard records ListLeaderboardRecords(ctx context.Context, in *ListLeaderboardRecordsRequest, opts ...grpc.CallOption) (*LeaderboardRecordList, error) // Fetch list of running matches. @@ -2651,6 +3048,10 @@ type NakamaClient interface { ListNotifications(ctx context.Context, in *ListNotificationsRequest, opts ...grpc.CallOption) (*NotificationList, error) // List publicly readable storage objects in a given collection. ListStorageObjects(ctx context.Context, in *ListStorageObjectsRequest, opts ...grpc.CallOption) (*StorageObjectList, error) + // List groups the current user belongs to. + ListUserGroups(ctx context.Context, in *ListUserGroupsRequest, opts ...grpc.CallOption) (*UserGroupList, error) + // Promote a set of users in a group to the next role up. + PromoteGroupUsers(ctx context.Context, in *PromoteGroupUsersRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Get storage objects. ReadStorageObjects(ctx context.Context, in *ReadStorageObjectsRequest, opts ...grpc.CallOption) (*StorageObjects, error) // Execute a Lua function on the server. @@ -2671,6 +3072,8 @@ type NakamaClient interface { UnlinkSteam(ctx context.Context, in *AccountSteam, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Update fields in the current user's account. UpdateAccount(ctx context.Context, in *UpdateAccountRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + // Update fields in a given group. + UpdateGroup(ctx context.Context, in *UpdateGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Write a record to a leaderboard. WriteLeaderboardRecord(ctx context.Context, in *WriteLeaderboardRecordRequest, opts ...grpc.CallOption) (*LeaderboardRecord, error) // Write objects into the storage engine. @@ -2694,6 +3097,15 @@ func (c *nakamaClient) AddFriends(ctx context.Context, in *AddFriendsRequest, op return out, nil } +func (c *nakamaClient) AddGroupUsers(ctx context.Context, in *AddGroupUsersRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/AddGroupUsers", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nakamaClient) AuthenticateCustom(ctx context.Context, in *AuthenticateCustomRequest, opts ...grpc.CallOption) (*Session, error) { out := new(Session) err := grpc.Invoke(ctx, "/nakama.api.Nakama/AuthenticateCustom", in, out, c.cc, opts...) @@ -2766,8 +3178,8 @@ func (c *nakamaClient) BlockFriends(ctx context.Context, in *BlockFriendsRequest return out, nil } -func (c *nakamaClient) CreateGroup(ctx context.Context, in *CreateGroupsRequest, opts ...grpc.CallOption) (*Groups, error) { - out := new(Groups) +func (c *nakamaClient) CreateGroup(ctx context.Context, in *CreateGroupRequest, opts ...grpc.CallOption) (*Group, error) { + out := new(Group) err := grpc.Invoke(ctx, "/nakama.api.Nakama/CreateGroup", in, out, c.cc, opts...) if err != nil { return nil, err @@ -2784,6 +3196,15 @@ func (c *nakamaClient) DeleteFriends(ctx context.Context, in *DeleteFriendsReque return out, nil } +func (c *nakamaClient) DeleteGroup(ctx context.Context, in *DeleteGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/DeleteGroup", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nakamaClient) DeleteLeaderboardRecord(ctx context.Context, in *DeleteLeaderboardRecordRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { out := new(google_protobuf1.Empty) err := grpc.Invoke(ctx, "/nakama.api.Nakama/DeleteLeaderboardRecord", in, out, c.cc, opts...) @@ -2847,6 +3268,33 @@ func (c *nakamaClient) ImportFacebookFriends(ctx context.Context, in *ImportFace return out, nil } +func (c *nakamaClient) JoinGroup(ctx context.Context, in *JoinGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/JoinGroup", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nakamaClient) KickGroupUsers(ctx context.Context, in *KickGroupUsersRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/KickGroupUsers", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nakamaClient) LeaveGroup(ctx context.Context, in *LeaveGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/LeaveGroup", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nakamaClient) LinkCustom(ctx context.Context, in *AccountCustom, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { out := new(google_protobuf1.Empty) err := grpc.Invoke(ctx, "/nakama.api.Nakama/LinkCustom", in, out, c.cc, opts...) @@ -2928,6 +3376,15 @@ func (c *nakamaClient) ListFriends(ctx context.Context, in *google_protobuf1.Emp return out, nil } +func (c *nakamaClient) ListGroupUsers(ctx context.Context, in *ListGroupUsersRequest, opts ...grpc.CallOption) (*GroupUserList, error) { + out := new(GroupUserList) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/ListGroupUsers", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nakamaClient) ListLeaderboardRecords(ctx context.Context, in *ListLeaderboardRecordsRequest, opts ...grpc.CallOption) (*LeaderboardRecordList, error) { out := new(LeaderboardRecordList) err := grpc.Invoke(ctx, "/nakama.api.Nakama/ListLeaderboardRecords", in, out, c.cc, opts...) @@ -2964,6 +3421,24 @@ func (c *nakamaClient) ListStorageObjects(ctx context.Context, in *ListStorageOb return out, nil } +func (c *nakamaClient) ListUserGroups(ctx context.Context, in *ListUserGroupsRequest, opts ...grpc.CallOption) (*UserGroupList, error) { + out := new(UserGroupList) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/ListUserGroups", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nakamaClient) PromoteGroupUsers(ctx context.Context, in *PromoteGroupUsersRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/PromoteGroupUsers", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nakamaClient) ReadStorageObjects(ctx context.Context, in *ReadStorageObjectsRequest, opts ...grpc.CallOption) (*StorageObjects, error) { out := new(StorageObjects) err := grpc.Invoke(ctx, "/nakama.api.Nakama/ReadStorageObjects", in, out, c.cc, opts...) @@ -3054,6 +3529,15 @@ func (c *nakamaClient) UpdateAccount(ctx context.Context, in *UpdateAccountReque return out, nil } +func (c *nakamaClient) UpdateGroup(ctx context.Context, in *UpdateGroupRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) + err := grpc.Invoke(ctx, "/nakama.api.Nakama/UpdateGroup", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *nakamaClient) WriteLeaderboardRecord(ctx context.Context, in *WriteLeaderboardRecordRequest, opts ...grpc.CallOption) (*LeaderboardRecord, error) { out := new(LeaderboardRecord) err := grpc.Invoke(ctx, "/nakama.api.Nakama/WriteLeaderboardRecord", in, out, c.cc, opts...) @@ -3077,6 +3561,8 @@ func (c *nakamaClient) WriteStorageObjects(ctx context.Context, in *WriteStorage type NakamaServer interface { // Add friends by ID or username to a user's account. AddFriends(context.Context, *AddFriendsRequest) (*google_protobuf1.Empty, error) + // Add users to a group. + AddGroupUsers(context.Context, *AddGroupUsersRequest) (*google_protobuf1.Empty, error) // Authenticate a user with a custom id against the server. AuthenticateCustom(context.Context, *AuthenticateCustomRequest) (*Session, error) // Authenticate a user with a device id against the server. @@ -3093,10 +3579,12 @@ type NakamaServer interface { AuthenticateSteam(context.Context, *AuthenticateSteamRequest) (*Session, error) // Block one or more users by ID or username. BlockFriends(context.Context, *BlockFriendsRequest) (*google_protobuf1.Empty, error) - // Create one or more new groups with the current user as the owner. - CreateGroup(context.Context, *CreateGroupsRequest) (*Groups, error) + // Create a new group with the current user as the owner. + CreateGroup(context.Context, *CreateGroupRequest) (*Group, error) // Delete one or more users by ID or username. DeleteFriends(context.Context, *DeleteFriendsRequest) (*google_protobuf1.Empty, error) + // Delete one or more groups by ID. + DeleteGroup(context.Context, *DeleteGroupRequest) (*google_protobuf1.Empty, error) // Delete a leaderboard record. DeleteLeaderboardRecord(context.Context, *DeleteLeaderboardRecordRequest) (*google_protobuf1.Empty, error) // Delete one or more users by ID or username. @@ -3111,6 +3599,12 @@ type NakamaServer interface { Healthcheck(context.Context, *google_protobuf1.Empty) (*google_protobuf1.Empty, error) // Import Facebook friends and add them to a user's account. ImportFacebookFriends(context.Context, *ImportFacebookFriendsRequest) (*google_protobuf1.Empty, error) + // Immediately join an open group, or request to join a closed one. + JoinGroup(context.Context, *JoinGroupRequest) (*google_protobuf1.Empty, error) + // Kick a set of users from a group. + KickGroupUsers(context.Context, *KickGroupUsersRequest) (*google_protobuf1.Empty, error) + // Leave a group the user is a member of. + LeaveGroup(context.Context, *LeaveGroupRequest) (*google_protobuf1.Empty, error) // Add a custom ID to the social profiles on the current user's account. LinkCustom(context.Context, *AccountCustom) (*google_protobuf1.Empty, error) // Add a device ID to the social profiles on the current user's account. @@ -3129,6 +3623,8 @@ type NakamaServer interface { ListChannelMessages(context.Context, *ListChannelMessagesRequest) (*ChannelMessageList, error) // List all friends for the current user. ListFriends(context.Context, *google_protobuf1.Empty) (*Friends, error) + // List all users that are part of a group. + ListGroupUsers(context.Context, *ListGroupUsersRequest) (*GroupUserList, error) // List leaderboard records ListLeaderboardRecords(context.Context, *ListLeaderboardRecordsRequest) (*LeaderboardRecordList, error) // Fetch list of running matches. @@ -3137,6 +3633,10 @@ type NakamaServer interface { ListNotifications(context.Context, *ListNotificationsRequest) (*NotificationList, error) // List publicly readable storage objects in a given collection. ListStorageObjects(context.Context, *ListStorageObjectsRequest) (*StorageObjectList, error) + // List groups the current user belongs to. + ListUserGroups(context.Context, *ListUserGroupsRequest) (*UserGroupList, error) + // Promote a set of users in a group to the next role up. + PromoteGroupUsers(context.Context, *PromoteGroupUsersRequest) (*google_protobuf1.Empty, error) // Get storage objects. ReadStorageObjects(context.Context, *ReadStorageObjectsRequest) (*StorageObjects, error) // Execute a Lua function on the server. @@ -3157,6 +3657,8 @@ type NakamaServer interface { UnlinkSteam(context.Context, *AccountSteam) (*google_protobuf1.Empty, error) // Update fields in the current user's account. UpdateAccount(context.Context, *UpdateAccountRequest) (*google_protobuf1.Empty, error) + // Update fields in a given group. + UpdateGroup(context.Context, *UpdateGroupRequest) (*google_protobuf1.Empty, error) // Write a record to a leaderboard. WriteLeaderboardRecord(context.Context, *WriteLeaderboardRecordRequest) (*LeaderboardRecord, error) // Write objects into the storage engine. @@ -3185,6 +3687,24 @@ func _Nakama_AddFriends_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Nakama_AddGroupUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddGroupUsersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).AddGroupUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/AddGroupUsers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).AddGroupUsers(ctx, req.(*AddGroupUsersRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Nakama_AuthenticateCustom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AuthenticateCustomRequest) if err := dec(in); err != nil { @@ -3330,7 +3850,7 @@ func _Nakama_BlockFriends_Handler(srv interface{}, ctx context.Context, dec func } func _Nakama_CreateGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateGroupsRequest) + in := new(CreateGroupRequest) if err := dec(in); err != nil { return nil, err } @@ -3342,7 +3862,7 @@ func _Nakama_CreateGroup_Handler(srv interface{}, ctx context.Context, dec func( FullMethod: "/nakama.api.Nakama/CreateGroup", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NakamaServer).CreateGroup(ctx, req.(*CreateGroupsRequest)) + return srv.(NakamaServer).CreateGroup(ctx, req.(*CreateGroupRequest)) } return interceptor(ctx, in, info, handler) } @@ -3365,6 +3885,24 @@ func _Nakama_DeleteFriends_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Nakama_DeleteGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteGroupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).DeleteGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/DeleteGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).DeleteGroup(ctx, req.(*DeleteGroupRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Nakama_DeleteLeaderboardRecord_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteLeaderboardRecordRequest) if err := dec(in); err != nil { @@ -3491,6 +4029,60 @@ func _Nakama_ImportFacebookFriends_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Nakama_JoinGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(JoinGroupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).JoinGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/JoinGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).JoinGroup(ctx, req.(*JoinGroupRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Nakama_KickGroupUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(KickGroupUsersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).KickGroupUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/KickGroupUsers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).KickGroupUsers(ctx, req.(*KickGroupUsersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Nakama_LeaveGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LeaveGroupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).LeaveGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/LeaveGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).LeaveGroup(ctx, req.(*LeaveGroupRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Nakama_LinkCustom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AccountCustom) if err := dec(in); err != nil { @@ -3653,6 +4245,24 @@ func _Nakama_ListFriends_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Nakama_ListGroupUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListGroupUsersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).ListGroupUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/ListGroupUsers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).ListGroupUsers(ctx, req.(*ListGroupUsersRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Nakama_ListLeaderboardRecords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListLeaderboardRecordsRequest) if err := dec(in); err != nil { @@ -3725,6 +4335,42 @@ func _Nakama_ListStorageObjects_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _Nakama_ListUserGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListUserGroupsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).ListUserGroups(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/ListUserGroups", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).ListUserGroups(ctx, req.(*ListUserGroupsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Nakama_PromoteGroupUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PromoteGroupUsersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).PromoteGroupUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/PromoteGroupUsers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).PromoteGroupUsers(ctx, req.(*PromoteGroupUsersRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Nakama_ReadStorageObjects_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReadStorageObjectsRequest) if err := dec(in); err != nil { @@ -3905,6 +4551,24 @@ func _Nakama_UpdateAccount_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Nakama_UpdateGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NakamaServer).UpdateGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/nakama.api.Nakama/UpdateGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NakamaServer).UpdateGroup(ctx, req.(*UpdateGroupRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Nakama_WriteLeaderboardRecord_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(WriteLeaderboardRecordRequest) if err := dec(in); err != nil { @@ -3949,6 +4613,10 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ MethodName: "AddFriends", Handler: _Nakama_AddFriends_Handler, }, + { + MethodName: "AddGroupUsers", + Handler: _Nakama_AddGroupUsers_Handler, + }, { MethodName: "AuthenticateCustom", Handler: _Nakama_AuthenticateCustom_Handler, @@ -3989,6 +4657,10 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ MethodName: "DeleteFriends", Handler: _Nakama_DeleteFriends_Handler, }, + { + MethodName: "DeleteGroup", + Handler: _Nakama_DeleteGroup_Handler, + }, { MethodName: "DeleteLeaderboardRecord", Handler: _Nakama_DeleteLeaderboardRecord_Handler, @@ -4017,6 +4689,18 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ MethodName: "ImportFacebookFriends", Handler: _Nakama_ImportFacebookFriends_Handler, }, + { + MethodName: "JoinGroup", + Handler: _Nakama_JoinGroup_Handler, + }, + { + MethodName: "KickGroupUsers", + Handler: _Nakama_KickGroupUsers_Handler, + }, + { + MethodName: "LeaveGroup", + Handler: _Nakama_LeaveGroup_Handler, + }, { MethodName: "LinkCustom", Handler: _Nakama_LinkCustom_Handler, @@ -4053,6 +4737,10 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ MethodName: "ListFriends", Handler: _Nakama_ListFriends_Handler, }, + { + MethodName: "ListGroupUsers", + Handler: _Nakama_ListGroupUsers_Handler, + }, { MethodName: "ListLeaderboardRecords", Handler: _Nakama_ListLeaderboardRecords_Handler, @@ -4069,6 +4757,14 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ MethodName: "ListStorageObjects", Handler: _Nakama_ListStorageObjects_Handler, }, + { + MethodName: "ListUserGroups", + Handler: _Nakama_ListUserGroups_Handler, + }, + { + MethodName: "PromoteGroupUsers", + Handler: _Nakama_PromoteGroupUsers_Handler, + }, { MethodName: "ReadStorageObjects", Handler: _Nakama_ReadStorageObjects_Handler, @@ -4109,6 +4805,10 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateAccount", Handler: _Nakama_UpdateAccount_Handler, }, + { + MethodName: "UpdateGroup", + Handler: _Nakama_UpdateGroup_Handler, + }, { MethodName: "WriteLeaderboardRecord", Handler: _Nakama_WriteLeaderboardRecord_Handler, @@ -4125,240 +4825,271 @@ var _Nakama_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("api/api.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 3755 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5b, 0xdd, 0x6f, 0x1c, 0xc9, - 0x71, 0xcf, 0xec, 0xf7, 0xd6, 0xf2, 0x63, 0xd9, 0x94, 0x78, 0xcb, 0xa5, 0x48, 0x51, 0x73, 0xd4, - 0x49, 0x47, 0xdf, 0x71, 0x2f, 0x94, 0x93, 0x3b, 0xd0, 0x80, 0x23, 0x8a, 0x22, 0x95, 0xcd, 0xc9, - 0xf4, 0x61, 0x48, 0xe9, 0x8c, 0x0b, 0x8c, 0xbd, 0xe6, 0x4c, 0x6b, 0x39, 0xe6, 0xec, 0xcc, 0x78, - 0x66, 0x96, 0x14, 0xef, 0x70, 0x70, 0x60, 0x04, 0xf0, 0x21, 0x0f, 0x86, 0xe1, 0xe4, 0x2d, 0x40, - 0x12, 0x38, 0x2f, 0x41, 0x80, 0x04, 0xf9, 0x03, 0xf2, 0x9a, 0x7f, 0x20, 0x01, 0x02, 0xc3, 0x4f, - 0x79, 0xc8, 0xa3, 0xff, 0x83, 0xbc, 0x04, 0xfd, 0x31, 0x33, 0x3d, 0x5f, 0xcb, 0x2f, 0x9d, 0xfd, - 0x22, 0x6d, 0xf7, 0x54, 0x77, 0x55, 0x77, 0x57, 0xd5, 0xaf, 0xaa, 0xba, 0x09, 0xd3, 0xd8, 0x35, - 0x7b, 0xd8, 0x35, 0x37, 0x5c, 0xcf, 0x09, 0x1c, 0x04, 0x36, 0x3e, 0xc1, 0x23, 0xbc, 0x81, 0x5d, - 0xb3, 0x7b, 0x67, 0xe8, 0x38, 0x43, 0x8b, 0xf4, 0x18, 0x85, 0x6d, 0x3b, 0x01, 0x0e, 0x4c, 0xc7, - 0xf6, 0x39, 0x65, 0x77, 0x49, 0x7c, 0x65, 0xad, 0xa3, 0xf1, 0xab, 0x1e, 0x19, 0xb9, 0xc1, 0xb9, - 0xf8, 0x78, 0x37, 0xfd, 0x31, 0x30, 0x47, 0xc4, 0x0f, 0xf0, 0xc8, 0x15, 0x04, 0x2b, 0x69, 0x82, - 0x33, 0x0f, 0xbb, 0x2e, 0xf1, 0xc2, 0xd9, 0xdf, 0x63, 0xff, 0xe9, 0xef, 0x0f, 0x89, 0xfd, 0xbe, - 0x7f, 0x86, 0x87, 0x43, 0xe2, 0xf5, 0x1c, 0x97, 0xf1, 0xcf, 0xca, 0xa2, 0xfe, 0x56, 0x81, 0xfa, - 0xb6, 0xae, 0x3b, 0x63, 0x3b, 0x40, 0x6b, 0x50, 0x19, 0xfb, 0xc4, 0xeb, 0x28, 0xab, 0xca, 0xc3, - 0xd6, 0x66, 0x7b, 0x23, 0x5e, 0xd0, 0xc6, 0x0b, 0x9f, 0x78, 0x1a, 0xfb, 0x8a, 0x16, 0xa0, 0x76, - 0x86, 0x2d, 0x8b, 0x04, 0x9d, 0xd2, 0xaa, 0xf2, 0xb0, 0xa9, 0x89, 0x16, 0xba, 0x05, 0x55, 0x32, - 0xc2, 0xa6, 0xd5, 0x29, 0xb3, 0x6e, 0xde, 0x40, 0x8f, 0xa0, 0x6e, 0x90, 0x53, 0x53, 0x27, 0x7e, - 0xa7, 0xb2, 0x5a, 0x7e, 0xd8, 0xda, 0x5c, 0x94, 0xa7, 0x15, 0x9c, 0x9f, 0x32, 0x0a, 0x2d, 0xa4, - 0x44, 0x4b, 0xd0, 0xd4, 0xc7, 0x7e, 0xe0, 0x8c, 0x06, 0xa6, 0xd1, 0xa9, 0xb2, 0xe9, 0x1a, 0xbc, - 0xa3, 0x6f, 0xa0, 0xef, 0x40, 0xeb, 0x94, 0x78, 0xe6, 0xab, 0xf3, 0x01, 0xdd, 0x99, 0x4e, 0x8d, - 0x09, 0xdb, 0xdd, 0xe0, 0xbb, 0xb2, 0x11, 0xee, 0xca, 0xc6, 0x61, 0xb8, 0x6d, 0x1a, 0x70, 0x72, - 0xda, 0xa1, 0xde, 0x85, 0x69, 0xc1, 0x73, 0x87, 0xcd, 0x87, 0x66, 0xa0, 0x64, 0x1a, 0x6c, 0xc5, - 0x4d, 0xad, 0x64, 0x1a, 0x12, 0x01, 0x17, 0x2a, 0x43, 0xf0, 0x18, 0xa6, 0x04, 0xc1, 0x2e, 0x5b, - 0x60, 0xb4, 0x6c, 0x45, 0x5e, 0x76, 0x17, 0x1a, 0x2e, 0xf6, 0xfd, 0x33, 0xc7, 0x33, 0xc4, 0x36, - 0x45, 0x6d, 0xf5, 0x01, 0xcc, 0x8a, 0x19, 0xf6, 0xb0, 0x4e, 0x8e, 0x1c, 0xe7, 0x84, 0x4e, 0x12, - 0x38, 0x27, 0xc4, 0x0e, 0x27, 0x61, 0x0d, 0xf5, 0x3f, 0x15, 0x98, 0x13, 0x94, 0xcf, 0xf0, 0x88, - 0xec, 0x10, 0x3b, 0x20, 0x1e, 0xdd, 0x1c, 0xd7, 0xc2, 0xe7, 0xc4, 0x1b, 0x44, 0x72, 0x35, 0x78, - 0x47, 0xdf, 0xa0, 0x1f, 0x8f, 0xc6, 0xb6, 0x61, 0x11, 0xfa, 0x51, 0x30, 0xe6, 0x1d, 0x7d, 0x03, - 0x7d, 0x0b, 0xe6, 0x22, 0x65, 0x1a, 0xf8, 0x44, 0x77, 0x6c, 0xc3, 0x67, 0xa7, 0x55, 0xd6, 0xda, - 0xd1, 0x87, 0x03, 0xde, 0x8f, 0x10, 0x54, 0x7c, 0x6c, 0x05, 0x9d, 0x0a, 0x9b, 0x84, 0xfd, 0x46, - 0x77, 0xa0, 0xe9, 0x9b, 0x43, 0x1b, 0x07, 0x63, 0x8f, 0x88, 0x73, 0x89, 0x3b, 0xd0, 0x1a, 0xcc, - 0xb8, 0xe3, 0x23, 0xcb, 0xd4, 0x07, 0x27, 0xe4, 0x7c, 0x30, 0xf6, 0x2c, 0x76, 0x36, 0x4d, 0x6d, - 0x8a, 0xf7, 0x7e, 0x4c, 0xce, 0x5f, 0x78, 0x96, 0x7a, 0x3f, 0xda, 0xe0, 0x67, 0xec, 0xc4, 0x0a, - 0xd6, 0xbe, 0x16, 0x6d, 0xf3, 0x41, 0x40, 0xf0, 0xa8, 0x80, 0x6a, 0x07, 0xe6, 0xb6, 0x0d, 0x63, - 0xcf, 0x33, 0x89, 0x6d, 0xf8, 0x1a, 0xf9, 0xf1, 0x98, 0xf8, 0x01, 0x6a, 0x43, 0xd9, 0x34, 0xfc, - 0x8e, 0xb2, 0x5a, 0x7e, 0xd8, 0xd4, 0xe8, 0x4f, 0x2a, 0x37, 0x55, 0x5d, 0x1b, 0x8f, 0x88, 0xdf, - 0x29, 0xb1, 0xfe, 0xb8, 0x43, 0xfd, 0x07, 0x05, 0x16, 0xb7, 0xc7, 0xc1, 0x31, 0xb1, 0x03, 0x53, - 0xc7, 0x01, 0xe1, 0x9a, 0x11, 0xce, 0xf6, 0x08, 0xea, 0x98, 0x0b, 0x22, 0xec, 0x22, 0x4f, 0x81, - 0xc5, 0x90, 0x90, 0x12, 0x6d, 0x42, 0x4d, 0xf7, 0x08, 0x0e, 0x08, 0x3b, 0x83, 0x3c, 0xf5, 0x7c, - 0xe2, 0x38, 0xd6, 0x4b, 0x6c, 0x8d, 0x89, 0x26, 0x28, 0xa9, 0xca, 0x84, 0x32, 0x09, 0x13, 0x8a, - 0xda, 0x19, 0x11, 0x85, 0xc1, 0x5c, 0x45, 0xc4, 0xd0, 0xc6, 0xbe, 0x29, 0x11, 0xff, 0x4e, 0x81, - 0x8e, 0x2c, 0x22, 0xb3, 0x8e, 0x50, 0xc2, 0xcd, 0xb4, 0x84, 0x9d, 0x1c, 0x09, 0xf9, 0x88, 0x6f, - 0x4c, 0xc0, 0x5f, 0x2b, 0xb0, 0x24, 0x0b, 0x18, 0x1a, 0x5f, 0x28, 0xe3, 0x1f, 0xa5, 0x65, 0x5c, - 0xca, 0x91, 0x31, 0x1a, 0xf4, 0x4d, 0x89, 0x49, 0xe7, 0x33, 0x47, 0xae, 0xe3, 0x71, 0xcb, 0xbb, - 0x60, 0x3e, 0x4e, 0xa9, 0xfe, 0x93, 0x02, 0xcb, 0xf2, 0xd2, 0x62, 0x6f, 0x11, 0x2e, 0xee, 0xc3, - 0xf4, 0xe2, 0x96, 0x73, 0x16, 0x27, 0x0d, 0xfb, 0x9d, 0x69, 0x32, 0x77, 0x02, 0x57, 0xd2, 0x64, - 0x31, 0xe4, 0x77, 0xa6, 0xc9, 0xcc, 0x01, 0x5d, 0x49, 0x93, 0xf9, 0x88, 0x6f, 0x4c, 0xc0, 0x5d, - 0x98, 0x7f, 0x62, 0x39, 0xfa, 0xc9, 0x0d, 0xfd, 0xde, 0xd7, 0x65, 0x98, 0xd9, 0x39, 0xc6, 0xb6, - 0x4d, 0xac, 0xef, 0x11, 0xdf, 0xc7, 0x43, 0x82, 0x96, 0x01, 0x74, 0xde, 0x13, 0x83, 0x4b, 0x53, - 0xf4, 0xf4, 0x0d, 0xfa, 0x79, 0xc4, 0x29, 0x63, 0x78, 0x69, 0x8a, 0x9e, 0xbe, 0x81, 0x7a, 0x50, - 0xd1, 0x1d, 0x83, 0xcb, 0x4b, 0xcd, 0x27, 0xbd, 0xca, 0xbe, 0x1d, 0x3c, 0xda, 0xe4, 0xcb, 0x64, - 0x84, 0x14, 0xad, 0x7c, 0x62, 0x1b, 0x1c, 0xca, 0x38, 0xd0, 0x34, 0x78, 0x47, 0xdf, 0x48, 0xec, - 0x40, 0x35, 0x65, 0x24, 0x1d, 0xa8, 0xeb, 0x8e, 0x1d, 0x10, 0x3b, 0x10, 0x18, 0x13, 0x36, 0x69, - 0x74, 0xc0, 0x77, 0x90, 0x47, 0x07, 0xf5, 0x8b, 0xa3, 0x03, 0x4e, 0x4e, 0x3b, 0xe8, 0xe0, 0xb1, - 0x6b, 0x44, 0x83, 0x1b, 0x17, 0x0f, 0xe6, 0xe4, 0x6c, 0xf0, 0x16, 0x00, 0x8d, 0xc2, 0x4c, 0x9f, - 0x89, 0xd5, 0xbc, 0xf0, 0xa4, 0x25, 0x6a, 0xf5, 0xe7, 0x0a, 0xa0, 0xe4, 0x51, 0x3c, 0x37, 0xfd, - 0x00, 0xfd, 0x31, 0x34, 0xc4, 0xee, 0xf2, 0x63, 0xa5, 0x13, 0x4a, 0xda, 0x96, 0x1c, 0xa1, 0x45, - 0xb4, 0xe8, 0x2e, 0xb4, 0x6c, 0xf2, 0x3a, 0x18, 0xe8, 0x63, 0xcf, 0x77, 0x3c, 0x71, 0x50, 0x40, - 0xbb, 0x76, 0x58, 0x0f, 0x25, 0x70, 0x3d, 0x72, 0x1a, 0x12, 0x70, 0x05, 0x03, 0xda, 0xc5, 0x09, - 0xd4, 0xbf, 0x2a, 0xc1, 0xfc, 0x0e, 0xdb, 0x98, 0x67, 0x9e, 0x33, 0x76, 0x23, 0x1d, 0x7b, 0x0c, - 0xb5, 0x21, 0xeb, 0x10, 0xf2, 0x3c, 0x4c, 0xc8, 0x93, 0x1d, 0xb0, 0xb1, 0x4f, 0xce, 0x58, 0x87, - 0x26, 0xc6, 0x75, 0xff, 0x4d, 0x81, 0x46, 0xd8, 0x49, 0x83, 0x0c, 0x76, 0xbe, 0x5c, 0xd3, 0xd8, - 0x6f, 0xb4, 0x0a, 0x2d, 0x83, 0xf8, 0xba, 0x67, 0xb2, 0x98, 0x55, 0x08, 0x2f, 0x77, 0xa1, 0x45, - 0x68, 0x58, 0xd8, 0x1e, 0x0e, 0x02, 0x3c, 0x14, 0xa2, 0xd7, 0x69, 0xfb, 0x10, 0x0f, 0xa9, 0xd2, - 0x8c, 0x48, 0x80, 0x0d, 0x1c, 0xe0, 0x50, 0xa1, 0xc2, 0x36, 0xd5, 0x5e, 0x7c, 0x8a, 0x03, 0xec, - 0xb1, 0xd8, 0x44, 0x84, 0x2f, 0xbc, 0xe7, 0x85, 0x67, 0x51, 0x9d, 0x72, 0x3d, 0xf3, 0x94, 0x9a, - 0x29, 0xd5, 0xa9, 0x86, 0x16, 0x36, 0xd5, 0x3d, 0xb8, 0xf5, 0x94, 0x58, 0x24, 0x20, 0x37, 0x34, - 0xb8, 0x67, 0xb0, 0xc2, 0xe7, 0x79, 0x4e, 0xb0, 0x41, 0xbc, 0x23, 0x07, 0x7b, 0x86, 0x46, 0x74, - 0x87, 0xfe, 0xcb, 0x67, 0xbc, 0x0f, 0x33, 0x56, 0xfc, 0x2d, 0xb6, 0xc1, 0x69, 0xa9, 0xb7, 0x6f, - 0xa8, 0x1b, 0xd0, 0xe5, 0x13, 0xed, 0x3b, 0x81, 0xf9, 0x8a, 0x3a, 0x29, 0x1a, 0xd1, 0x17, 0x8a, - 0xa5, 0xea, 0x70, 0x9b, 0xd3, 0x1f, 0x04, 0x8e, 0x87, 0x87, 0xe4, 0xfb, 0x47, 0x3f, 0x22, 0x7a, - 0xd0, 0x37, 0xd0, 0x0a, 0x80, 0xee, 0x58, 0x16, 0xd1, 0xd9, 0x56, 0x73, 0x5e, 0x52, 0x0f, 0x9d, - 0xea, 0x84, 0x9c, 0x8b, 0x33, 0xa0, 0x3f, 0xe9, 0x2e, 0x9d, 0x52, 0xbd, 0x75, 0xec, 0x70, 0xeb, - 0x45, 0x53, 0x1d, 0xc0, 0x52, 0x0e, 0x13, 0x49, 0x73, 0xc0, 0x61, 0x3d, 0x83, 0x50, 0xb8, 0xd6, - 0xe6, 0x3d, 0x59, 0x7b, 0x72, 0x25, 0xd4, 0x9a, 0x8e, 0xf8, 0xe5, 0xab, 0xff, 0xa8, 0x40, 0x8d, - 0x9f, 0xc0, 0x25, 0x33, 0x95, 0x5b, 0x50, 0xf5, 0x83, 0xd0, 0xed, 0x56, 0x35, 0xde, 0x50, 0x7f, - 0x08, 0xd5, 0x03, 0xfa, 0x03, 0xdd, 0x86, 0xb9, 0x83, 0xc3, 0xed, 0xc3, 0xdd, 0xc1, 0x8b, 0xfd, - 0x83, 0x4f, 0x76, 0x77, 0xfa, 0x7b, 0xfd, 0xdd, 0xa7, 0xed, 0x3f, 0x40, 0x00, 0xb5, 0x3d, 0xad, - 0xbf, 0xbb, 0xff, 0xb4, 0xad, 0xa0, 0x59, 0x68, 0xf5, 0xf7, 0x5f, 0xf6, 0x0f, 0x77, 0x07, 0x07, - 0xbb, 0xfb, 0x87, 0xed, 0x12, 0x9a, 0x87, 0x59, 0xd1, 0xa1, 0xed, 0xee, 0xec, 0xf6, 0x5f, 0xee, - 0x3e, 0x6d, 0x97, 0x51, 0x0b, 0xea, 0x4f, 0x9e, 0x7f, 0x7f, 0xe7, 0xe3, 0xdd, 0xa7, 0xed, 0x8a, - 0xfa, 0x21, 0xd4, 0x85, 0x9a, 0xa0, 0xf7, 0xa0, 0xfe, 0x8a, 0xff, 0x14, 0xeb, 0x45, 0xb2, 0xa0, - 0x9c, 0x4a, 0x0b, 0x49, 0x54, 0x03, 0x66, 0x9f, 0x91, 0x80, 0x8a, 0x7f, 0x5d, 0x05, 0x43, 0xf7, - 0x60, 0xea, 0x95, 0x08, 0x50, 0xd8, 0x2e, 0x97, 0x19, 0x41, 0x2b, 0xec, 0xa3, 0x9b, 0xf8, 0xdb, - 0x12, 0x54, 0xb9, 0xed, 0xa5, 0x12, 0x1b, 0xe6, 0xfb, 0xa9, 0x01, 0x3b, 0x9e, 0xe4, 0xdc, 0x45, - 0x4f, 0xdf, 0x88, 0x4c, 0xb5, 0x5c, 0x6c, 0xaa, 0x95, 0xc9, 0xa6, 0x5a, 0x2d, 0x36, 0xd5, 0xda, - 0x44, 0x53, 0xad, 0x4f, 0x30, 0xd5, 0x46, 0xc2, 0x54, 0xe9, 0x91, 0x73, 0x70, 0x6e, 0xf2, 0x23, - 0xe7, 0x00, 0x9c, 0x02, 0x05, 0xb8, 0x09, 0x28, 0xb4, 0xae, 0x02, 0x0a, 0xea, 0x23, 0xa8, 0x71, - 0x7f, 0x88, 0xde, 0x4d, 0x79, 0xce, 0x39, 0x59, 0x17, 0x12, 0x2e, 0x52, 0xfd, 0x99, 0x02, 0x77, - 0xfa, 0x2c, 0xb2, 0x0b, 0xc3, 0xcd, 0x94, 0xe3, 0xb9, 0x66, 0xa8, 0xfa, 0x01, 0x54, 0x3d, 0xe2, - 0x8b, 0xc4, 0x7d, 0x32, 0x38, 0x71, 0x42, 0xf5, 0x3f, 0xca, 0x30, 0x97, 0x71, 0x56, 0x97, 0xf4, - 0x52, 0xf4, 0xec, 0x9d, 0x33, 0x9b, 0x48, 0xea, 0x54, 0x67, 0xed, 0xbe, 0x81, 0x3e, 0x4a, 0x45, - 0x37, 0xad, 0xcd, 0x3b, 0x19, 0x61, 0x0e, 0x02, 0xcf, 0xb4, 0x87, 0x5c, 0x9c, 0x18, 0xf9, 0xa9, - 0x4d, 0xeb, 0x8e, 0x47, 0x98, 0xb2, 0x95, 0x35, 0xde, 0xa0, 0xba, 0xe4, 0x8f, 0x8f, 0xf8, 0x87, - 0x2a, 0xfb, 0x10, 0xb5, 0x69, 0x90, 0x61, 0x8f, 0x47, 0x03, 0xfe, 0xb1, 0xc6, 0xd4, 0xa2, 0x61, - 0x8f, 0x47, 0x07, 0xe1, 0xc0, 0x48, 0x09, 0xeb, 0x29, 0x25, 0x4c, 0x69, 0x4d, 0xe3, 0x26, 0x5a, - 0xd3, 0xbc, 0x52, 0x28, 0xf1, 0x1d, 0x68, 0x91, 0xd7, 0xae, 0xe9, 0x9d, 0x5f, 0x5a, 0x5f, 0x39, - 0x39, 0x1b, 0x8c, 0xa0, 0xe2, 0x61, 0xfb, 0x84, 0x29, 0x6a, 0x59, 0x63, 0xbf, 0xd5, 0xff, 0x56, - 0xe0, 0x76, 0xe6, 0x1c, 0x59, 0x88, 0xf1, 0x21, 0xd4, 0x3d, 0xd6, 0x0a, 0xf5, 0x32, 0x91, 0x18, - 0x64, 0x81, 0x2a, 0xa4, 0x46, 0x4f, 0x60, 0x9a, 0x9f, 0x6e, 0x38, 0xbc, 0x74, 0x99, 0xe1, 0x53, - 0x6c, 0x8c, 0x26, 0xe6, 0x48, 0xc5, 0x29, 0xe5, 0x8b, 0xe2, 0x94, 0x4a, 0x26, 0x4e, 0xf9, 0x0b, - 0x05, 0xe6, 0x9f, 0x9b, 0xf6, 0xc9, 0x9b, 0x4b, 0xe6, 0xae, 0x9c, 0x7c, 0xfd, 0xbb, 0x02, 0x5d, - 0xba, 0x95, 0xc9, 0x68, 0x2c, 0xb2, 0xd5, 0x0b, 0x42, 0xea, 0x3f, 0x84, 0xaa, 0x65, 0x8e, 0xcc, - 0xd0, 0x26, 0x27, 0x06, 0xcd, 0x9c, 0x12, 0x7d, 0x1b, 0xea, 0xaf, 0x1c, 0xef, 0x0c, 0x7b, 0x86, - 0xb0, 0x9d, 0x49, 0x52, 0x86, 0xa4, 0x68, 0x01, 0x6a, 0x89, 0x5d, 0x14, 0x2d, 0xf5, 0x5f, 0x15, - 0x58, 0xa6, 0xe2, 0x67, 0xce, 0xca, 0xbf, 0x5a, 0x50, 0x42, 0xed, 0x2c, 0x34, 0xf7, 0x10, 0x9a, - 0x1a, 0xc2, 0xde, 0xfd, 0x78, 0x99, 0xe5, 0x4b, 0x2f, 0xb3, 0x48, 0xe0, 0x7f, 0x29, 0x01, 0xa2, - 0x02, 0x7f, 0x0f, 0x07, 0xfa, 0x71, 0xbc, 0xcf, 0x11, 0x07, 0xe5, 0xd2, 0x1c, 0x1e, 0xc3, 0x34, - 0x1e, 0x07, 0xc7, 0x8e, 0x67, 0x06, 0x38, 0x30, 0x4f, 0x2f, 0x93, 0x9e, 0x25, 0x07, 0xa0, 0x4d, - 0xa8, 0x5a, 0xf8, 0x88, 0x58, 0x97, 0x72, 0x62, 0x9c, 0x94, 0x05, 0xf5, 0xa6, 0x3d, 0xf0, 0xcd, - 0x2f, 0x88, 0xd0, 0xb2, 0x89, 0xb2, 0xd6, 0x47, 0xa6, 0x7d, 0x60, 0x7e, 0x41, 0xd8, 0x38, 0xfc, - 0x9a, 0x8f, 0xab, 0x5e, 0x66, 0x1c, 0x7e, 0x4d, 0xc7, 0xa9, 0xaf, 0xa1, 0x43, 0xb7, 0x2b, 0x37, - 0x54, 0xbc, 0xc6, 0xa6, 0xbd, 0x0b, 0x6d, 0x1d, 0xeb, 0xc7, 0x04, 0x1f, 0x59, 0x24, 0x99, 0x60, - 0xcc, 0x46, 0xfd, 0xc2, 0x38, 0xff, 0x5e, 0x81, 0x45, 0xca, 0x3a, 0x3f, 0x20, 0x7c, 0x0b, 0xea, - 0xd4, 0xab, 0xc7, 0xfa, 0x54, 0xa3, 0xcd, 0x4c, 0x50, 0x5a, 0xca, 0x04, 0xa5, 0x6f, 0x50, 0x97, - 0x7e, 0xa1, 0x40, 0x95, 0xe9, 0x11, 0x05, 0xab, 0x11, 0xfd, 0x11, 0x8b, 0x53, 0x67, 0xed, 0x3e, - 0x0d, 0x36, 0x73, 0xd4, 0xa4, 0xf1, 0x26, 0x54, 0x01, 0x41, 0x25, 0x52, 0x83, 0xaa, 0xc6, 0x7e, - 0xab, 0x1f, 0x41, 0x93, 0x49, 0xc4, 0xbc, 0xf3, 0xb7, 0x80, 0x4b, 0x41, 0x72, 0xa3, 0x06, 0x46, - 0xa7, 0x85, 0x14, 0xea, 0xff, 0x28, 0x30, 0x25, 0x9f, 0x72, 0x26, 0xc2, 0xeb, 0x40, 0xdd, 0x1f, - 0xb3, 0x43, 0x08, 0xf1, 0x58, 0x34, 0xe5, 0x7c, 0xba, 0x9c, 0xcc, 0xa7, 0x91, 0xc8, 0xe9, 0x85, - 0x88, 0xd9, 0xb4, 0xbd, 0x9a, 0x4a, 0xdb, 0x53, 0xa8, 0x59, 0xbb, 0x12, 0x6a, 0xae, 0x24, 0x72, - 0xe8, 0x3a, 0xdb, 0x67, 0x39, 0x4f, 0xfe, 0x0a, 0xda, 0xf2, 0x0a, 0xd9, 0x1e, 0x7d, 0x17, 0xa6, - 0x6d, 0x59, 0xb7, 0xc5, 0x4e, 0x25, 0xea, 0x32, 0xf2, 0x20, 0x2d, 0x49, 0x7e, 0x15, 0x85, 0xfe, - 0x1c, 0xe6, 0x35, 0x82, 0x8d, 0x9b, 0x67, 0x51, 0x92, 0xee, 0x97, 0x65, 0xdd, 0x57, 0xff, 0x1c, - 0x16, 0x33, 0x1c, 0x22, 0x8b, 0xf9, 0x6e, 0x4e, 0x0a, 0x75, 0x57, 0x5e, 0x66, 0x8e, 0x70, 0x72, - 0x02, 0xf5, 0x67, 0x50, 0xd6, 0x5c, 0x3d, 0x4f, 0x2d, 0x5c, 0x7c, 0x6e, 0x39, 0x38, 0x0a, 0xd3, - 0x44, 0x93, 0x1a, 0xc5, 0x71, 0x10, 0xb8, 0x03, 0x2a, 0xbd, 0xd0, 0x0b, 0xda, 0xfe, 0x98, 0x9c, - 0xab, 0x2f, 0xa1, 0x7e, 0x40, 0x7c, 0x9a, 0xf8, 0x31, 0xe5, 0x61, 0x47, 0xc8, 0x27, 0x6d, 0x68, - 0x61, 0x33, 0x2e, 0xda, 0x97, 0xa4, 0xa2, 0x3d, 0x55, 0x9f, 0xb1, 0xe1, 0x0e, 0xf8, 0x97, 0xb0, - 0xb6, 0x65, 0xb8, 0x87, 0xac, 0xa2, 0xff, 0xeb, 0x12, 0x4c, 0x27, 0x96, 0xf0, 0x06, 0x77, 0x97, - 0xca, 0x73, 0x4a, 0xed, 0x4f, 0x78, 0x01, 0xde, 0x90, 0x53, 0xda, 0x6a, 0x22, 0xa5, 0x45, 0x0f, - 0x60, 0xd6, 0x25, 0xde, 0xc8, 0x64, 0xeb, 0x1c, 0x78, 0x04, 0x1b, 0x22, 0x80, 0x9c, 0x89, 0xbb, - 0xe9, 0x9e, 0x53, 0x1d, 0x92, 0x08, 0xcf, 0x3c, 0x33, 0xe0, 0xa5, 0xa7, 0xaa, 0x26, 0x4d, 0xf0, - 0x29, 0xed, 0xfe, 0xfd, 0x45, 0x95, 0xea, 0x19, 0xb4, 0x13, 0x3b, 0xbb, 0xad, 0x9f, 0xbc, 0xc9, - 0x02, 0x80, 0xbc, 0xed, 0x95, 0x84, 0x52, 0xef, 0xc2, 0x5c, 0x9a, 0xb1, 0x8f, 0x3e, 0x80, 0x0a, - 0xd6, 0x4f, 0x42, 0x35, 0xbe, 0x23, 0xab, 0x71, 0x9a, 0x58, 0x63, 0x94, 0xea, 0x2e, 0xcc, 0x24, - 0xed, 0x02, 0x3d, 0x82, 0x3a, 0xd7, 0xee, 0x70, 0x9a, 0xc5, 0xc2, 0x69, 0xb4, 0x90, 0x52, 0xfd, - 0x3c, 0x25, 0x0d, 0x73, 0x22, 0xd7, 0x99, 0x49, 0x42, 0x95, 0x52, 0x02, 0x55, 0xfe, 0xaf, 0x04, - 0xb7, 0x5e, 0xb0, 0x7d, 0x17, 0x81, 0x66, 0x68, 0xc0, 0x72, 0xda, 0xa3, 0x5c, 0x29, 0xed, 0xf9, - 0x13, 0x98, 0x32, 0x4c, 0xdf, 0xb5, 0xf0, 0xf9, 0x80, 0x8d, 0x2e, 0x5d, 0x62, 0x74, 0x4b, 0x8c, - 0xd8, 0xc7, 0x4c, 0x73, 0xe4, 0x8c, 0xfa, 0x32, 0x18, 0x25, 0xe5, 0xdb, 0x1f, 0x4a, 0x59, 0x7c, - 0xe5, 0x12, 0x43, 0xa3, 0x1c, 0xff, 0x23, 0x68, 0x58, 0x0e, 0x77, 0xb4, 0x22, 0x66, 0xb9, 0x60, - 0xc1, 0x21, 0x35, 0x1d, 0x49, 0x55, 0xfc, 0x0b, 0xc7, 0x0e, 0x31, 0xe4, 0x82, 0x91, 0x21, 0xb5, - 0xfa, 0xcb, 0x0a, 0x54, 0x5e, 0xf8, 0xc4, 0xcb, 0xf8, 0x39, 0xb9, 0xa0, 0x5c, 0x4a, 0x15, 0x94, - 0xef, 0xa5, 0xf6, 0xb7, 0x2c, 0x4a, 0x19, 0xd2, 0x0e, 0x26, 0x6b, 0x12, 0x95, 0x74, 0x4d, 0x62, - 0x72, 0xa5, 0x23, 0xda, 0x05, 0x51, 0xe9, 0x88, 0xd6, 0xd9, 0x95, 0xd6, 0x29, 0x12, 0xd0, 0xb0, - 0x9d, 0x48, 0x4e, 0x1b, 0xa9, 0xe4, 0xf4, 0x2e, 0xb4, 0xa4, 0x52, 0x0f, 0xf3, 0x04, 0x4d, 0x0d, - 0xe2, 0x4a, 0x0f, 0xf5, 0xb2, 0x7c, 0xbf, 0xe8, 0x67, 0xe0, 0xa3, 0x79, 0x47, 0xdf, 0x40, 0x6f, - 0xc3, 0xf4, 0x10, 0x8f, 0x88, 0xce, 0x2e, 0x7b, 0x28, 0x41, 0x8b, 0xdf, 0xd4, 0xc6, 0x9d, 0x3c, - 0x7f, 0xf7, 0x03, 0x82, 0xd9, 0x25, 0xfc, 0x94, 0x88, 0x17, 0x68, 0xbb, 0xcf, 0x92, 0x09, 0xc7, - 0xb6, 0x4c, 0x9b, 0x74, 0xa6, 0x99, 0xc7, 0x17, 0x2d, 0xba, 0x47, 0xc4, 0x18, 0x92, 0x01, 0xcf, - 0xbc, 0x66, 0x98, 0x07, 0x6c, 0xd2, 0x9e, 0x9d, 0xbc, 0x3a, 0xcc, 0xec, 0x4d, 0x7c, 0x5f, 0xfb, - 0x4a, 0xbe, 0xaf, 0x07, 0x55, 0x56, 0x59, 0x43, 0xef, 0x40, 0x95, 0x1e, 0x7a, 0x68, 0xe6, 0xd9, - 0xd2, 0x21, 0xff, 0xac, 0x7e, 0x5d, 0x82, 0x65, 0xe6, 0xb0, 0x6f, 0x58, 0xab, 0x45, 0x3f, 0x80, - 0x1a, 0xcf, 0x90, 0x85, 0xcd, 0x3e, 0x96, 0x39, 0x4e, 0xe4, 0x90, 0x4d, 0x9f, 0x19, 0xb9, 0x26, - 0xe6, 0xeb, 0xbe, 0x82, 0x85, 0x7c, 0x8a, 0xb8, 0x48, 0xa2, 0x14, 0x15, 0x49, 0x4a, 0xa9, 0x22, - 0x89, 0xac, 0x6a, 0xe5, 0xa4, 0xaa, 0xa9, 0x7f, 0x59, 0x02, 0xc4, 0xe6, 0xbd, 0x29, 0x2e, 0x47, - 0xf0, 0x5b, 0x2e, 0x80, 0xdf, 0x4a, 0x12, 0x50, 0x9e, 0x66, 0xe1, 0xf7, 0x12, 0x89, 0x4f, 0x1a, - 0x9b, 0xf7, 0x72, 0xb0, 0xb9, 0x76, 0xf1, 0x34, 0x69, 0xe0, 0x56, 0x5f, 0x42, 0x37, 0xbb, 0x0b, - 0x7e, 0xec, 0xda, 0x53, 0x00, 0xb2, 0x92, 0x39, 0xe7, 0x7c, 0x14, 0xd9, 0xfc, 0xcd, 0x7d, 0xa8, - 0xed, 0x33, 0x52, 0xf4, 0x29, 0x40, 0xfc, 0x9c, 0x01, 0x25, 0xaf, 0x68, 0xd3, 0xcf, 0x1c, 0xba, - 0x0b, 0x19, 0xe9, 0x77, 0x47, 0x6e, 0x70, 0xae, 0xa2, 0x9f, 0xfe, 0xd7, 0xff, 0xfe, 0x75, 0x69, - 0x4a, 0x85, 0xde, 0xe9, 0x66, 0x8f, 0x17, 0x97, 0xd1, 0x4f, 0x15, 0x40, 0xd9, 0x27, 0x0e, 0xe8, - 0x7e, 0x82, 0x43, 0xd1, 0x13, 0x88, 0xee, 0x7c, 0x02, 0x0b, 0x79, 0xd4, 0xa7, 0x7e, 0xc0, 0xd8, - 0xac, 0xab, 0x77, 0x29, 0x1b, 0x51, 0x3f, 0xe9, 0x61, 0x69, 0x8e, 0x1e, 0x7f, 0xb1, 0xb3, 0x15, - 0x15, 0x57, 0xd2, 0x42, 0x88, 0x07, 0x36, 0x85, 0x42, 0x24, 0x1e, 0x39, 0x5c, 0x57, 0x08, 0xfe, - 0xa4, 0x28, 0x16, 0xe2, 0x27, 0x30, 0x97, 0x79, 0xa5, 0x80, 0xd6, 0x8a, 0x44, 0x90, 0x1f, 0x31, - 0xe4, 0x4b, 0xd0, 0x63, 0x12, 0xbc, 0xab, 0xae, 0x14, 0x4a, 0xc0, 0x1e, 0x04, 0xc5, 0x02, 0x7c, - 0xad, 0xc0, 0xad, 0xbc, 0x67, 0x08, 0xe8, 0x41, 0x91, 0x10, 0xa9, 0xda, 0x56, 0xbe, 0x1c, 0x9b, - 0x4c, 0x8e, 0xf7, 0xd4, 0x7b, 0x85, 0x72, 0x84, 0xd8, 0x10, 0x8b, 0xf2, 0x73, 0x05, 0x16, 0xf2, - 0x9f, 0x0d, 0xa0, 0x77, 0x8b, 0x84, 0xc9, 0x3c, 0x2d, 0xc8, 0x17, 0xe7, 0xdb, 0x4c, 0x9c, 0x0d, - 0xf5, 0xed, 0x42, 0x71, 0x62, 0xa8, 0x29, 0xd6, 0x10, 0xf1, 0x42, 0xa8, 0x50, 0x43, 0x12, 0x8f, - 0x07, 0xae, 0xab, 0x21, 0xdc, 0x8a, 0x0a, 0x35, 0x84, 0x3f, 0x3f, 0x2a, 0xd4, 0x10, 0xf9, 0x71, - 0xc0, 0x75, 0x35, 0x84, 0xa1, 0x6a, 0x2c, 0x00, 0x86, 0x29, 0xf9, 0x7a, 0x1f, 0x25, 0x52, 0xbc, - 0x9c, 0x8b, 0xff, 0x42, 0x4f, 0xd0, 0x61, 0x9c, 0x91, 0xda, 0x8e, 0x3d, 0x41, 0xef, 0x88, 0x8e, - 0x47, 0x3f, 0x80, 0x96, 0x74, 0x59, 0x9b, 0xe4, 0x90, 0x73, 0x8b, 0xdb, 0x45, 0x99, 0xcb, 0x0a, - 0x5f, 0xbd, 0xc5, 0x66, 0x9f, 0x51, 0x9b, 0x74, 0x76, 0x76, 0x73, 0xb1, 0xa5, 0xac, 0xa3, 0x1f, - 0xc2, 0x74, 0xe2, 0xae, 0x14, 0xad, 0x66, 0xef, 0xf8, 0xae, 0xe6, 0xc8, 0xd6, 0x65, 0x47, 0xf6, - 0x33, 0x05, 0xde, 0x2a, 0xb8, 0x43, 0x45, 0xeb, 0x59, 0x4e, 0x45, 0xd0, 0x5a, 0xc8, 0xf3, 0x21, - 0xe3, 0xa9, 0xae, 0xaf, 0x52, 0x9e, 0x12, 0x90, 0xf7, 0xbe, 0x4c, 0x62, 0xfd, 0x57, 0xc8, 0x81, - 0xf9, 0x9c, 0x3b, 0x58, 0xf4, 0x4e, 0x56, 0x88, 0xbc, 0xca, 0xdb, 0x45, 0x67, 0xb6, 0xce, 0xce, - 0x4c, 0x2e, 0x54, 0xa0, 0xd3, 0xf0, 0x16, 0x3a, 0x95, 0x04, 0x3d, 0xb8, 0xe0, 0x12, 0xf5, 0x42, - 0x96, 0xcb, 0x8c, 0xe5, 0x5b, 0x5d, 0x44, 0x59, 0xfa, 0x7c, 0x68, 0xcf, 0x60, 0x13, 0xd1, 0x13, - 0xdd, 0x07, 0x78, 0x46, 0x82, 0xf0, 0x8d, 0x68, 0xc1, 0x24, 0x49, 0xd5, 0x17, 0xc4, 0xea, 0x3c, - 0x9b, 0x79, 0x1a, 0xb5, 0x24, 0xd5, 0x47, 0xcf, 0xa1, 0x11, 0xde, 0x73, 0xa2, 0x44, 0x55, 0x3e, - 0x75, 0xfb, 0xd9, 0x9d, 0x4b, 0xc7, 0x66, 0xbe, 0xda, 0x66, 0x13, 0x02, 0x6a, 0xd0, 0x09, 0xd9, - 0x1d, 0xef, 0x01, 0xb4, 0xfe, 0x94, 0x60, 0x2b, 0x38, 0xd6, 0x8f, 0x89, 0x7e, 0x52, 0x28, 0x5e, - 0xd1, 0xda, 0x85, 0x12, 0xa3, 0xa9, 0xde, 0xb1, 0x34, 0xcb, 0x4f, 0xe0, 0x76, 0xee, 0xfd, 0x1b, - 0x4a, 0x3c, 0x77, 0x98, 0x74, 0x45, 0x57, 0xc8, 0x70, 0x8d, 0x31, 0x5c, 0x51, 0xe7, 0x25, 0x9b, - 0xcc, 0x7a, 0x66, 0x1d, 0xe0, 0xb9, 0x69, 0x9f, 0x08, 0x98, 0x2e, 0x7e, 0x71, 0x58, 0xc8, 0x46, - 0x65, 0x6c, 0xee, 0xa8, 0x6f, 0xc9, 0x4e, 0xc7, 0x32, 0xed, 0x93, 0x10, 0x95, 0x95, 0xf5, 0x90, - 0x89, 0x80, 0xe1, 0xe2, 0x37, 0x83, 0xd7, 0x60, 0x22, 0x50, 0x57, 0x59, 0x47, 0x9f, 0x43, 0x93, - 0x32, 0xe1, 0x38, 0x5b, 0xf8, 0xea, 0xaf, 0x90, 0xc5, 0x3d, 0xc6, 0x62, 0x49, 0x5d, 0xc8, 0xb0, - 0xe0, 0xb0, 0xaa, 0xac, 0x23, 0x1f, 0xa6, 0xe4, 0x1b, 0xa0, 0xa4, 0x33, 0xcb, 0xb9, 0x1b, 0x2a, - 0xe4, 0xb5, 0xce, 0x78, 0xad, 0xa9, 0x8b, 0x19, 0x5e, 0xd9, 0x03, 0x72, 0x60, 0x86, 0x4e, 0x2d, - 0x21, 0xe6, 0xe4, 0x07, 0x75, 0x85, 0x4c, 0xdf, 0x61, 0x4c, 0x57, 0xd5, 0xa5, 0x0c, 0x53, 0x09, - 0x20, 0xe3, 0xc3, 0x12, 0x88, 0x58, 0xfc, 0x2c, 0xee, 0x1a, 0x87, 0x25, 0x00, 0x30, 0x3e, 0x2c, - 0x0e, 0x79, 0x85, 0x0f, 0xdb, 0xae, 0x71, 0x58, 0x1c, 0xe1, 0x94, 0x75, 0xe4, 0xc1, 0x7c, 0xce, - 0x5d, 0x59, 0xd2, 0x6b, 0x16, 0x5f, 0xa6, 0x75, 0x57, 0x8a, 0x9f, 0x3f, 0xd1, 0x51, 0x49, 0x87, - 0x23, 0x2e, 0xd9, 0xd0, 0x3e, 0xb4, 0xe8, 0xc7, 0xd0, 0x86, 0x2f, 0xe5, 0xc1, 0x04, 0x71, 0x88, - 0x41, 0x48, 0xc6, 0xa0, 0x5f, 0x28, 0xb0, 0x90, 0x7f, 0x63, 0x96, 0x0c, 0x9b, 0x26, 0xde, 0xaa, - 0x75, 0xef, 0x4d, 0xbc, 0x28, 0x65, 0xab, 0x11, 0x60, 0x84, 0x2e, 0x06, 0xa3, 0x4f, 0xf9, 0x12, - 0xc5, 0x95, 0x18, 0x5a, 0x49, 0x8b, 0x91, 0xbc, 0x2b, 0xeb, 0xde, 0xce, 0xdc, 0x22, 0x30, 0x7e, - 0x73, 0x8c, 0x5f, 0x0b, 0x31, 0x44, 0x67, 0xb7, 0x0a, 0xe8, 0xc7, 0x30, 0x97, 0xb9, 0x3c, 0x4a, - 0x06, 0x43, 0x45, 0x77, 0x4b, 0xdd, 0x3b, 0x45, 0x05, 0x78, 0xc6, 0x4b, 0xe0, 0x1c, 0xca, 0xe2, - 0xdc, 0xaf, 0x14, 0x7e, 0xbf, 0x97, 0x82, 0xb9, 0xfb, 0x69, 0xa6, 0xf9, 0x20, 0xb7, 0x5c, 0x58, - 0xb7, 0x63, 0x6c, 0xf7, 0x18, 0xdb, 0xc7, 0xa8, 0x23, 0x63, 0xdd, 0x97, 0x71, 0xd6, 0xfa, 0xd5, - 0x67, 0x6b, 0x48, 0x2d, 0xfa, 0xd6, 0xfb, 0x52, 0xd4, 0x37, 0x29, 0xfa, 0xa3, 0x6c, 0x9d, 0x3e, - 0x29, 0x63, 0x61, 0x1d, 0xbf, 0xdb, 0x2d, 0x94, 0xd1, 0x57, 0x17, 0x98, 0x80, 0x6d, 0xb5, 0x25, - 0x09, 0x41, 0x0d, 0xe7, 0x33, 0xa8, 0x6b, 0xae, 0xbe, 0x37, 0xb6, 0x75, 0x34, 0x9b, 0xe0, 0xe2, - 0xea, 0xdd, 0x74, 0x87, 0xfa, 0x3e, 0x9b, 0xe4, 0x81, 0x3a, 0x45, 0x27, 0xf1, 0x5c, 0xbd, 0xf7, - 0xa5, 0x69, 0x7c, 0xb5, 0x15, 0x96, 0xf5, 0x3f, 0xa3, 0x70, 0x27, 0x7d, 0x40, 0x43, 0x98, 0x7a, - 0x61, 0x5b, 0x37, 0xc2, 0x9b, 0x10, 0xd6, 0x12, 0xbe, 0x73, 0x6c, 0xa7, 0x10, 0x27, 0x62, 0x74, - 0x7d, 0xcc, 0x99, 0xc4, 0x28, 0x46, 0x1d, 0x03, 0x5a, 0x9c, 0xd1, 0x75, 0x71, 0xe7, 0x6d, 0xc6, - 0x66, 0x59, 0xed, 0xe4, 0xb0, 0x89, 0x90, 0x67, 0x04, 0x33, 0x9c, 0x4b, 0x84, 0x3d, 0x93, 0x5e, - 0x19, 0x5c, 0x0d, 0x02, 0x04, 0xaf, 0x08, 0x79, 0x18, 0xd0, 0xb5, 0x39, 0xbb, 0x9b, 0xa3, 0x8e, - 0xf0, 0x2c, 0xea, 0x72, 0x0e, 0xcb, 0x24, 0xee, 0x44, 0x47, 0x76, 0x7d, 0xe4, 0x99, 0x74, 0x64, - 0x31, 0xf6, 0x44, 0x47, 0x76, 0x5d, 0xf4, 0x99, 0x74, 0x64, 0x11, 0xfe, 0x60, 0x98, 0x4e, 0x54, - 0xe6, 0x93, 0xe9, 0x49, 0x5e, 0xd1, 0xbe, 0x90, 0x9f, 0xb0, 0xd4, 0xae, 0x1c, 0xdc, 0x52, 0x16, - 0x7f, 0xa3, 0xc0, 0x42, 0x7e, 0x5d, 0x2f, 0x09, 0x0f, 0x13, 0x6b, 0x7f, 0xdd, 0xc9, 0xef, 0x68, - 0xa2, 0xb4, 0xf6, 0x42, 0x68, 0xd8, 0x12, 0xd5, 0x42, 0x14, 0xc0, 0x7c, 0x4e, 0xf9, 0x2a, 0x89, - 0xbc, 0xc5, 0xf5, 0xad, 0x09, 0x7e, 0x75, 0x5b, 0x3f, 0xf1, 0x93, 0x9b, 0x11, 0xbb, 0xad, 0x27, - 0x7f, 0x5b, 0xfa, 0xe5, 0xf6, 0x6f, 0x14, 0x34, 0x86, 0x69, 0x5e, 0xe2, 0x5a, 0xdd, 0xfe, 0xa4, - 0xbf, 0x7a, 0xba, 0xa9, 0x0e, 0xe0, 0xde, 0xe1, 0x31, 0x59, 0x0d, 0x3b, 0xd9, 0x4d, 0xba, 0xbf, - 0xfa, 0xce, 0xea, 0x8e, 0x63, 0x07, 0x9e, 0x79, 0x34, 0x0e, 0x1c, 0xcf, 0x47, 0x6b, 0xc7, 0x41, - 0xe0, 0xfa, 0x5b, 0xbd, 0xde, 0xd0, 0x0c, 0x8e, 0xc7, 0x47, 0x1b, 0xba, 0x33, 0xea, 0x1d, 0x13, - 0xcf, 0x31, 0x75, 0x0b, 0x1f, 0xf9, 0x3d, 0x2e, 0x50, 0xf7, 0xd6, 0x31, 0xb1, 0x2c, 0xe7, 0x71, - 0xfc, 0x81, 0xd2, 0x6d, 0x96, 0x37, 0x37, 0x3e, 0x58, 0x57, 0x94, 0xcd, 0x36, 0x76, 0x5d, 0x4b, - 0x40, 0x4b, 0xef, 0x47, 0xbe, 0x63, 0x6f, 0x65, 0x7a, 0xbc, 0x2d, 0x58, 0x12, 0x82, 0xf8, 0xc4, - 0x3b, 0x25, 0xde, 0xaa, 0xe1, 0xe8, 0xe3, 0x11, 0xb1, 0xf9, 0x5f, 0xca, 0xa1, 0xa5, 0x50, 0x8c, - 0x24, 0x8b, 0x9e, 0xe1, 0xe8, 0x3e, 0x2c, 0xea, 0xce, 0x68, 0x43, 0xfa, 0x10, 0xef, 0xd2, 0x93, - 0x26, 0x9f, 0x74, 0xdb, 0x35, 0x3f, 0x51, 0x3e, 0x2b, 0x63, 0xd7, 0xfc, 0x55, 0xa9, 0xb2, 0xff, - 0xf1, 0x27, 0x4f, 0xfe, 0xb9, 0x24, 0xea, 0x7d, 0x47, 0x35, 0xa6, 0x52, 0x8f, 0xfe, 0x3f, 0x00, - 0x00, 0xff, 0xff, 0x69, 0xb0, 0x53, 0xdc, 0x4a, 0x38, 0x00, 0x00, + // 4244 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3b, 0x4d, 0x6f, 0x1c, 0x47, + 0x76, 0xe9, 0xf9, 0xe0, 0xcc, 0xbc, 0x21, 0xa9, 0x61, 0x51, 0xa2, 0x87, 0x43, 0x52, 0xa2, 0xda, + 0x92, 0x25, 0x73, 0x2d, 0x8e, 0x42, 0x6d, 0x62, 0x43, 0x0b, 0x38, 0xa2, 0xc8, 0x91, 0x32, 0x96, + 0x44, 0x2b, 0x4d, 0x4a, 0x0e, 0x1c, 0x2c, 0xc6, 0xcd, 0xee, 0xd2, 0xb0, 0x3d, 0x3d, 0xdd, 0xed, + 0xee, 0x1e, 0x4a, 0xb4, 0x20, 0x38, 0x58, 0x04, 0x58, 0x9f, 0x16, 0x8b, 0x4d, 0x0e, 0x01, 0x02, + 0x24, 0x8b, 0xdd, 0xcb, 0x22, 0x40, 0xf2, 0x0b, 0x72, 0x0d, 0x72, 0xc8, 0x2d, 0x01, 0x82, 0x45, + 0x4e, 0x39, 0xe4, 0x98, 0x1f, 0x10, 0x60, 0x2f, 0x41, 0x7d, 0x74, 0x77, 0xf5, 0xd7, 0xcc, 0x90, + 0x94, 0x9d, 0x8b, 0x34, 0x55, 0xf5, 0xea, 0xbd, 0xd7, 0xaf, 0xde, 0x57, 0xbd, 0x7a, 0x84, 0x39, + 0xd5, 0x31, 0xda, 0xaa, 0x63, 0x6c, 0x3a, 0xae, 0xed, 0xdb, 0x08, 0x2c, 0x75, 0xa0, 0x0e, 0xd5, + 0x4d, 0xd5, 0x31, 0x5a, 0xab, 0x7d, 0xdb, 0xee, 0x9b, 0xb8, 0x4d, 0x21, 0x2c, 0xcb, 0xf6, 0x55, + 0xdf, 0xb0, 0x2d, 0x8f, 0x41, 0xb6, 0x56, 0xf8, 0x2a, 0x1d, 0x1d, 0x8e, 0x5e, 0xb4, 0xf1, 0xd0, + 0xf1, 0x4f, 0xf8, 0xe2, 0x95, 0xe4, 0xa2, 0x6f, 0x0c, 0xb1, 0xe7, 0xab, 0x43, 0x87, 0x03, 0x5c, + 0x4e, 0x02, 0xbc, 0x74, 0x55, 0xc7, 0xc1, 0x6e, 0x80, 0xfd, 0x03, 0xfa, 0x9f, 0x76, 0xab, 0x8f, + 0xad, 0x5b, 0xde, 0x4b, 0xb5, 0xdf, 0xc7, 0x6e, 0xdb, 0x76, 0x28, 0xfd, 0x34, 0x2f, 0xf2, 0xff, + 0x48, 0x50, 0xd9, 0xd6, 0x34, 0x7b, 0x64, 0xf9, 0xe8, 0x1a, 0x94, 0x46, 0x1e, 0x76, 0x9b, 0xd2, + 0xba, 0x74, 0xb3, 0xbe, 0xd5, 0xd8, 0x8c, 0x3e, 0x68, 0xf3, 0x99, 0x87, 0x5d, 0x85, 0xae, 0xa2, + 0x25, 0x98, 0x79, 0xa9, 0x9a, 0x26, 0xf6, 0x9b, 0x85, 0x75, 0xe9, 0x66, 0x4d, 0xe1, 0x23, 0x74, + 0x11, 0xca, 0x78, 0xa8, 0x1a, 0x66, 0xb3, 0x48, 0xa7, 0xd9, 0x00, 0xdd, 0x81, 0x8a, 0x8e, 0x8f, + 0x0d, 0x0d, 0x7b, 0xcd, 0xd2, 0x7a, 0xf1, 0x66, 0x7d, 0x6b, 0x59, 0x44, 0xcb, 0x29, 0xef, 0x52, + 0x08, 0x25, 0x80, 0x44, 0x2b, 0x50, 0xd3, 0x46, 0x9e, 0x6f, 0x0f, 0x7b, 0x86, 0xde, 0x2c, 0x53, + 0x74, 0x55, 0x36, 0xd1, 0xd5, 0xd1, 0x8f, 0xa0, 0x7e, 0x8c, 0x5d, 0xe3, 0xc5, 0x49, 0x8f, 0x48, + 0xa6, 0x39, 0x43, 0x99, 0x6d, 0x6d, 0x32, 0xa9, 0x6c, 0x06, 0x52, 0xd9, 0x3c, 0x08, 0xc4, 0xa6, + 0x00, 0x03, 0x27, 0x13, 0xf2, 0x15, 0x98, 0xe3, 0x34, 0x77, 0x28, 0x3e, 0x34, 0x0f, 0x05, 0x43, + 0xa7, 0x5f, 0x5c, 0x53, 0x0a, 0x86, 0x2e, 0x00, 0x30, 0xa6, 0x52, 0x00, 0xf7, 0x60, 0x96, 0x03, + 0x74, 0xe8, 0x07, 0x86, 0x9f, 0x2d, 0x89, 0x9f, 0xdd, 0x82, 0xaa, 0xa3, 0x7a, 0xde, 0x4b, 0xdb, + 0xd5, 0xb9, 0x98, 0xc2, 0xb1, 0x7c, 0x03, 0x2e, 0x70, 0x0c, 0x0f, 0x54, 0x0d, 0x1f, 0xda, 0xf6, + 0x80, 0x20, 0xf1, 0xed, 0x01, 0xb6, 0x02, 0x24, 0x74, 0x20, 0xff, 0x9b, 0x04, 0x0b, 0x1c, 0xf2, + 0xa1, 0x3a, 0xc4, 0x3b, 0xd8, 0xf2, 0xb1, 0x4b, 0x84, 0xe3, 0x98, 0xea, 0x09, 0x76, 0x7b, 0x21, + 0x5f, 0x55, 0x36, 0xd1, 0xd5, 0xc9, 0xe2, 0xe1, 0xc8, 0xd2, 0x4d, 0x4c, 0x16, 0x39, 0x61, 0x36, + 0xd1, 0xd5, 0xd1, 0x0f, 0x60, 0x21, 0x54, 0xa6, 0x9e, 0x87, 0x35, 0xdb, 0xd2, 0x3d, 0x7a, 0x5a, + 0x45, 0xa5, 0x11, 0x2e, 0xec, 0xb3, 0x79, 0x84, 0xa0, 0xe4, 0xa9, 0xa6, 0xdf, 0x2c, 0x51, 0x24, + 0xf4, 0x37, 0x5a, 0x85, 0x9a, 0x67, 0xf4, 0x2d, 0xd5, 0x1f, 0xb9, 0x98, 0x9f, 0x4b, 0x34, 0x81, + 0xae, 0xc1, 0xbc, 0x33, 0x3a, 0x34, 0x0d, 0xad, 0x37, 0xc0, 0x27, 0xbd, 0x91, 0x6b, 0xd2, 0xb3, + 0xa9, 0x29, 0xb3, 0x6c, 0xf6, 0x11, 0x3e, 0x79, 0xe6, 0x9a, 0xf2, 0xf5, 0x50, 0xc0, 0x0f, 0xe9, + 0x89, 0xe5, 0x7c, 0xfb, 0xb5, 0x50, 0xcc, 0xfb, 0x3e, 0x56, 0x87, 0x39, 0x50, 0x3b, 0xb0, 0xb0, + 0xad, 0xeb, 0x0f, 0x5c, 0x03, 0x5b, 0xba, 0xa7, 0xe0, 0xaf, 0x46, 0xd8, 0xf3, 0x51, 0x03, 0x8a, + 0x86, 0xee, 0x35, 0xa5, 0xf5, 0xe2, 0xcd, 0x9a, 0x42, 0x7e, 0x12, 0xbe, 0x89, 0xea, 0x5a, 0xea, + 0x10, 0x7b, 0xcd, 0x02, 0x9d, 0x8f, 0x26, 0xe4, 0xc7, 0x70, 0x71, 0x5b, 0xd7, 0x1f, 0xba, 0xf6, + 0xc8, 0x21, 0x6a, 0x1e, 0xe2, 0x59, 0x86, 0x6a, 0x9f, 0x4c, 0x46, 0x72, 0xae, 0xd0, 0x71, 0x57, + 0x27, 0x4b, 0x64, 0x7f, 0x8f, 0xd0, 0x61, 0xf8, 0x2a, 0x64, 0xdc, 0xd5, 0x3d, 0xf9, 0x97, 0x12, + 0x2c, 0x6f, 0x8f, 0xfc, 0x23, 0x6c, 0xf9, 0x86, 0xa6, 0xfa, 0x98, 0xe9, 0x59, 0x80, 0xf3, 0x0e, + 0x54, 0x54, 0xf6, 0x59, 0xdc, 0xca, 0xb2, 0xcc, 0x81, 0x6f, 0x09, 0x20, 0xd1, 0x16, 0xcc, 0x68, + 0x2e, 0x56, 0x7d, 0x4c, 0x4f, 0x34, 0x4b, 0xd9, 0xef, 0xdb, 0xb6, 0xf9, 0x5c, 0x35, 0x47, 0x58, + 0xe1, 0x90, 0x44, 0x01, 0x83, 0x2f, 0xe4, 0x06, 0x19, 0x8e, 0x53, 0x2c, 0x72, 0xf3, 0x3b, 0x0d, + 0x8b, 0x81, 0xc5, 0x7e, 0x57, 0x2c, 0xfe, 0xad, 0x04, 0x4d, 0x91, 0x45, 0x6a, 0x6b, 0x01, 0x87, + 0x5b, 0x49, 0x0e, 0x9b, 0x19, 0x1c, 0xb2, 0x1d, 0xdf, 0x19, 0x83, 0xbf, 0x95, 0x60, 0x45, 0x64, + 0x30, 0x30, 0xe5, 0x80, 0xc7, 0x3f, 0x48, 0xf2, 0xb8, 0x92, 0xc1, 0x63, 0xb8, 0xe9, 0xbb, 0x62, + 0x93, 0xe0, 0x33, 0x86, 0x8e, 0xed, 0x32, 0x3b, 0x9e, 0x80, 0x8f, 0x41, 0xca, 0xbf, 0x91, 0x60, + 0x4d, 0xfc, 0xb4, 0xc8, 0xf7, 0x04, 0x1f, 0xf7, 0x61, 0xf2, 0xe3, 0xd6, 0x32, 0x3e, 0x4e, 0xd8, + 0xf6, 0xbd, 0x69, 0x32, 0x73, 0x29, 0xa7, 0xd2, 0x64, 0xbe, 0xe5, 0x7b, 0xd3, 0x64, 0xea, 0xce, + 0x4e, 0xa5, 0xc9, 0x6c, 0xc7, 0x77, 0xc6, 0x60, 0x07, 0x16, 0xef, 0x9b, 0xb6, 0x36, 0x38, 0xa7, + 0x17, 0xfd, 0xb6, 0x08, 0xf3, 0x3b, 0x47, 0xaa, 0x65, 0x61, 0xf3, 0x09, 0xf6, 0x3c, 0xb5, 0x8f, + 0xd1, 0x1a, 0x80, 0xc6, 0x66, 0x22, 0x17, 0x5a, 0xe3, 0x33, 0x5d, 0x9d, 0x2c, 0x0f, 0x19, 0x64, + 0x14, 0xac, 0x6a, 0x7c, 0xa6, 0xab, 0xa3, 0x36, 0x94, 0x34, 0x5b, 0x67, 0xfc, 0x12, 0xf3, 0x49, + 0x7e, 0x65, 0xd7, 0xf2, 0xef, 0x6c, 0xb1, 0xcf, 0xa4, 0x80, 0x24, 0xf6, 0x79, 0xd8, 0xd2, 0x59, + 0x60, 0x64, 0x61, 0xab, 0xca, 0x26, 0xba, 0x7a, 0x4c, 0x02, 0xe5, 0x84, 0x91, 0x34, 0xa1, 0xa2, + 0xd9, 0x96, 0x8f, 0x2d, 0x9f, 0x47, 0xac, 0x60, 0x48, 0x72, 0x0d, 0x26, 0x41, 0x96, 0x6b, 0x54, + 0x26, 0xe7, 0x1a, 0x0c, 0x9c, 0x4c, 0x90, 0xcd, 0x23, 0x47, 0x0f, 0x37, 0x57, 0x27, 0x6f, 0x66, + 0xe0, 0x74, 0xf3, 0x5d, 0x00, 0x92, 0xd3, 0x19, 0x1e, 0x65, 0xab, 0x36, 0xf1, 0xa4, 0x05, 0x68, + 0xf9, 0x67, 0x12, 0xa0, 0xf8, 0x51, 0x3c, 0x36, 0x3c, 0x1f, 0xfd, 0x21, 0x54, 0xb9, 0x74, 0xd9, + 0xb1, 0x12, 0x84, 0x82, 0xb6, 0xc5, 0x77, 0x28, 0x21, 0x2c, 0xba, 0x02, 0x75, 0x0b, 0xbf, 0xf2, + 0x7b, 0xda, 0xc8, 0xf5, 0x6c, 0x97, 0x1f, 0x14, 0x90, 0xa9, 0x1d, 0x3a, 0x43, 0x00, 0x1c, 0x17, + 0x1f, 0x07, 0x00, 0x4c, 0xc1, 0x80, 0x4c, 0x31, 0x00, 0xf9, 0xaf, 0x09, 0x43, 0x54, 0x30, 0x34, + 0xca, 0x06, 0x2a, 0x86, 0xa0, 0x44, 0xcf, 0x83, 0x69, 0x06, 0xfd, 0x8d, 0xd6, 0xa1, 0xae, 0x63, + 0x4f, 0x73, 0x0d, 0x9a, 0xb1, 0x72, 0x62, 0xe2, 0x14, 0x89, 0xbd, 0xa6, 0x6a, 0xf5, 0x7b, 0xbe, + 0xda, 0xe7, 0xa4, 0x2a, 0x64, 0x7c, 0xa0, 0xf6, 0x89, 0x46, 0xa9, 0xc7, 0xaa, 0xaf, 0xba, 0x34, + 0xfb, 0x60, 0x2a, 0x50, 0x63, 0x33, 0xcf, 0x5c, 0x93, 0xd0, 0xb3, 0x1d, 0x6c, 0xd1, 0xf3, 0xaf, + 0x2a, 0xf4, 0xb7, 0xfc, 0x00, 0x2e, 0xee, 0x62, 0x13, 0xfb, 0xf8, 0x9c, 0xea, 0xdf, 0x06, 0xc4, + 0xf0, 0xc4, 0xbe, 0x30, 0x3f, 0x85, 0x90, 0x1f, 0xc2, 0x65, 0xb6, 0xe1, 0x31, 0x56, 0x75, 0xec, + 0x1e, 0xda, 0xaa, 0xab, 0x2b, 0x58, 0xb3, 0xc9, 0xbf, 0x6c, 0xf3, 0x75, 0x98, 0x37, 0xa3, 0xb5, + 0x08, 0xc5, 0x9c, 0x30, 0xdb, 0xd5, 0xe5, 0x4d, 0x68, 0x31, 0x44, 0x7b, 0xb6, 0x6f, 0xbc, 0x20, + 0x3e, 0x86, 0xa4, 0xf7, 0xb9, 0xdf, 0x21, 0x6b, 0x70, 0x89, 0xc1, 0xef, 0xfb, 0xb6, 0xab, 0xf6, + 0xf1, 0xa7, 0x87, 0x5f, 0x62, 0xcd, 0xef, 0xea, 0xe8, 0x32, 0x80, 0x66, 0x9b, 0x26, 0xd6, 0xa8, + 0xe4, 0x19, 0x2d, 0x61, 0x86, 0xa0, 0x1a, 0xe0, 0x13, 0x7e, 0x24, 0xe4, 0x27, 0x31, 0x9c, 0x63, + 0xa2, 0x76, 0xb6, 0x15, 0x9c, 0x04, 0x1f, 0xca, 0x3d, 0x58, 0xc9, 0x20, 0x12, 0x72, 0x75, 0x0f, + 0xc0, 0xa6, 0x33, 0xbd, 0x80, 0xb9, 0xfa, 0xd6, 0x55, 0x51, 0x19, 0x33, 0x39, 0x54, 0x6a, 0x36, + 0xff, 0xe5, 0xc9, 0xbf, 0x96, 0x60, 0x86, 0x1d, 0xd9, 0x94, 0xd7, 0x96, 0x8b, 0x50, 0xf6, 0xfc, + 0xc0, 0x6b, 0x96, 0x15, 0x36, 0x90, 0x7f, 0x0c, 0xe5, 0x7d, 0xf2, 0x03, 0x5d, 0x82, 0x85, 0xfd, + 0x83, 0xed, 0x83, 0x4e, 0xef, 0xd9, 0xde, 0xfe, 0xd3, 0xce, 0x4e, 0xf7, 0x41, 0xb7, 0xb3, 0xdb, + 0xf8, 0x3d, 0x04, 0x30, 0xf3, 0x40, 0xe9, 0x76, 0xf6, 0x76, 0x1b, 0x12, 0xba, 0x00, 0xf5, 0xee, + 0xde, 0xf3, 0xee, 0x41, 0xa7, 0xb7, 0xdf, 0xd9, 0x3b, 0x68, 0x14, 0xd0, 0x22, 0x5c, 0xe0, 0x13, + 0x4a, 0x67, 0xa7, 0xd3, 0x7d, 0xde, 0xd9, 0x6d, 0x14, 0x51, 0x1d, 0x2a, 0xf7, 0x1f, 0x7f, 0xba, + 0xf3, 0xa8, 0xb3, 0xdb, 0x28, 0xc9, 0x1f, 0x42, 0x85, 0xeb, 0x15, 0xfa, 0x00, 0x2a, 0x2f, 0xd8, + 0x4f, 0xfe, 0xbd, 0x48, 0x64, 0x94, 0x41, 0x29, 0x01, 0x88, 0xac, 0xc3, 0x85, 0x87, 0xd8, 0x8f, + 0xa5, 0xa3, 0xa7, 0xd4, 0x48, 0x74, 0x15, 0x66, 0x5f, 0xf0, 0xfc, 0x82, 0x4a, 0xb9, 0x48, 0x01, + 0xea, 0xc1, 0x1c, 0x11, 0xe2, 0x6f, 0x8a, 0x50, 0xa6, 0xfa, 0x9a, 0xbc, 0xe5, 0x50, 0xd7, 0x4d, + 0x0c, 0xd6, 0x76, 0x05, 0xdf, 0xcc, 0x67, 0xba, 0x7a, 0x68, 0xb9, 0xc5, 0x7c, 0xcb, 0x2d, 0x8d, + 0xb7, 0xdc, 0x72, 0xdc, 0x72, 0x5b, 0xc4, 0x37, 0xf9, 0xaa, 0xae, 0xfa, 0x2a, 0xf7, 0xc1, 0xe1, + 0x38, 0x61, 0xd5, 0x95, 0xa4, 0x55, 0x6f, 0x72, 0xab, 0xae, 0x4e, 0xf4, 0x91, 0x14, 0x8e, 0xa0, + 0xc3, 0x7a, 0x1f, 0xf7, 0x58, 0xd8, 0xad, 0x51, 0x6d, 0xa8, 0x91, 0x99, 0x1d, 0x1a, 0x5e, 0x57, + 0xa0, 0x36, 0x54, 0x5f, 0xf1, 0x55, 0xa0, 0xab, 0xd5, 0xa1, 0xfa, 0x8a, 0x2d, 0x26, 0xe2, 0x41, + 0xfd, 0x3c, 0xf1, 0x60, 0xf6, 0x34, 0xf1, 0x40, 0xfe, 0x5f, 0x09, 0xe6, 0xc2, 0x2b, 0x0a, 0x75, + 0xe7, 0xbb, 0x50, 0x67, 0xbe, 0x85, 0x1c, 0x79, 0xa0, 0x54, 0xef, 0x8a, 0x4a, 0x15, 0x83, 0x8f, + 0x46, 0x0a, 0xf4, 0xc3, 0xbb, 0x4e, 0xeb, 0x97, 0x12, 0xd4, 0xc2, 0x95, 0x73, 0x99, 0xd2, 0x67, + 0x13, 0x4c, 0x69, 0x1e, 0x60, 0xff, 0xd9, 0xd3, 0x8e, 0xb2, 0xbd, 0xfb, 0xa4, 0xbb, 0xd7, 0x90, + 0x50, 0x0d, 0xca, 0xec, 0x67, 0x81, 0x58, 0xd9, 0x93, 0xce, 0x93, 0xfb, 0x1d, 0xa5, 0x51, 0x44, + 0x0d, 0x98, 0xfd, 0xe4, 0xd3, 0xee, 0x5e, 0x4f, 0xe9, 0xfc, 0xc9, 0xb3, 0xce, 0xfe, 0x41, 0xa3, + 0x24, 0xff, 0x54, 0x82, 0xd5, 0x2e, 0x4d, 0x4d, 0x83, 0x7c, 0x39, 0xe1, 0xab, 0xcf, 0x98, 0x6b, + 0xdf, 0x86, 0xb2, 0x8b, 0x3d, 0x5e, 0xc7, 0x18, 0xaf, 0x39, 0x0c, 0x50, 0xbe, 0x05, 0x8d, 0x4f, + 0x6c, 0xc3, 0x9a, 0xd6, 0xc5, 0x3f, 0x81, 0x4b, 0x8f, 0x0c, 0x6d, 0xf0, 0xb6, 0x6e, 0x96, 0xff, + 0x5c, 0x84, 0x85, 0x54, 0xb0, 0x98, 0x32, 0x4a, 0x10, 0xbc, 0xf6, 0x4b, 0x0b, 0x0b, 0xe6, 0x5c, + 0xa1, 0xe3, 0xae, 0x8e, 0x3e, 0x4a, 0x24, 0x87, 0xf5, 0xad, 0xd5, 0x94, 0x28, 0xf6, 0x7d, 0xd7, + 0xb0, 0xfa, 0x4c, 0x18, 0x51, 0xe2, 0x44, 0x14, 0x41, 0xb3, 0x5d, 0x4c, 0x8d, 0xbd, 0xa8, 0xb0, + 0x01, 0xb1, 0x65, 0x6f, 0x74, 0xc8, 0x16, 0xca, 0x74, 0x21, 0x1c, 0x13, 0xeb, 0xb2, 0x46, 0xc3, + 0x1e, 0x5b, 0x9c, 0x61, 0xd6, 0x65, 0x8d, 0x86, 0xfb, 0xc1, 0xc6, 0xd0, 0x09, 0x54, 0x12, 0x4e, + 0x20, 0x61, 0x79, 0xd5, 0xf3, 0x58, 0x5e, 0xed, 0x54, 0x99, 0xd8, 0x8f, 0xa0, 0x8e, 0x5f, 0x39, + 0x86, 0xcb, 0xeb, 0x4d, 0x30, 0x79, 0x33, 0x03, 0xa7, 0x9b, 0x11, 0x94, 0x5c, 0xd5, 0x1a, 0x50, + 0x4f, 0x51, 0x54, 0xe8, 0x6f, 0xf9, 0x3f, 0x24, 0xb8, 0x94, 0x3a, 0x47, 0x6a, 0xd2, 0x1f, 0x42, + 0xc5, 0xa5, 0xa3, 0xc0, 0x9c, 0x63, 0xf7, 0xaa, 0x74, 0xa2, 0x10, 0x40, 0xa3, 0xfb, 0x30, 0xc7, + 0x4e, 0x37, 0xd8, 0x5e, 0x98, 0x66, 0xfb, 0x2c, 0xdd, 0xa3, 0x70, 0x1c, 0x89, 0x34, 0xaf, 0x38, + 0x29, 0xcd, 0x2b, 0xa5, 0xd2, 0xbc, 0x4d, 0xaa, 0x9f, 0xc7, 0x53, 0xa7, 0x40, 0x7f, 0x2e, 0xc1, + 0xe2, 0x63, 0xc3, 0x1a, 0xbc, 0xbd, 0xbb, 0xf3, 0xa9, 0xef, 0xba, 0xff, 0x24, 0x41, 0x8b, 0x88, + 0x3e, 0x9e, 0xfc, 0x86, 0x86, 0x3a, 0xe1, 0x06, 0xf3, 0xfb, 0x50, 0x36, 0x8d, 0xa1, 0x11, 0x78, + 0x90, 0xb1, 0x77, 0x14, 0x06, 0x89, 0x7e, 0x08, 0x95, 0x17, 0xb6, 0xfb, 0x52, 0x75, 0x75, 0x6e, + 0x6b, 0xe3, 0xb8, 0x0c, 0x40, 0xd1, 0x12, 0xcc, 0xc4, 0xa4, 0xce, 0x47, 0xf2, 0x16, 0x5c, 0x22, + 0xdc, 0x9f, 0xc6, 0xc3, 0xc8, 0xff, 0x28, 0xc1, 0x1a, 0xd9, 0x94, 0xd2, 0x07, 0xef, 0x74, 0x89, + 0x27, 0xb1, 0xe5, 0xc0, 0xa5, 0x04, 0xbe, 0xaa, 0xca, 0x7d, 0x8a, 0x17, 0x89, 0xa6, 0x38, 0xb5, + 0x68, 0xf2, 0x3e, 0xf2, 0x1f, 0x0a, 0x80, 0x08, 0xc3, 0x4f, 0x54, 0x5f, 0x3b, 0x8a, 0xce, 0x26, + 0xa4, 0x20, 0x4d, 0x4d, 0xe1, 0x1e, 0xcc, 0xa9, 0x23, 0xff, 0xc8, 0x76, 0x0d, 0x5f, 0xf5, 0x8d, + 0xe3, 0x69, 0x6e, 0xd0, 0xf1, 0x0d, 0x68, 0x0b, 0xca, 0xa6, 0x7a, 0x88, 0xcd, 0xa9, 0x1c, 0x25, + 0x03, 0xa5, 0xf7, 0x2e, 0xc3, 0xea, 0x79, 0xc6, 0xd7, 0x98, 0x6b, 0xe6, 0x58, 0x5e, 0x2b, 0x43, + 0xc3, 0xda, 0x37, 0xbe, 0xc6, 0x74, 0x9f, 0xfa, 0x8a, 0xed, 0x2b, 0x4f, 0xb3, 0x4f, 0x7d, 0x45, + 0xf6, 0xc9, 0xaf, 0xa0, 0x49, 0xc4, 0x95, 0x79, 0x1d, 0x38, 0x83, 0xd0, 0xde, 0x87, 0x86, 0xa6, + 0x6a, 0x47, 0x58, 0x3d, 0x34, 0x71, 0xfc, 0x0e, 0x78, 0x21, 0x9c, 0xe7, 0x0e, 0xe0, 0xef, 0x24, + 0x58, 0x26, 0xa4, 0xb3, 0x93, 0xfe, 0x77, 0xa0, 0xc2, 0x43, 0x1b, 0xd7, 0xa7, 0x19, 0x16, 0xd9, + 0x12, 0x17, 0x8f, 0x42, 0xea, 0xe2, 0xf1, 0x16, 0x75, 0xe9, 0x36, 0x33, 0x18, 0x62, 0x2b, 0xd4, + 0x68, 0x26, 0x32, 0x27, 0xff, 0x5c, 0x82, 0x32, 0xd5, 0x3c, 0x62, 0x53, 0x43, 0xf2, 0x43, 0xb0, + 0x29, 0x3a, 0xee, 0x92, 0x2b, 0x48, 0x86, 0x62, 0x55, 0xdf, 0x86, 0xf2, 0x20, 0x28, 0x85, 0x8a, + 0x53, 0x56, 0xe8, 0x6f, 0xf9, 0x23, 0xa8, 0x51, 0x8e, 0x68, 0xcc, 0xf8, 0x01, 0x30, 0x2e, 0xc2, + 0x4b, 0xfd, 0x82, 0xe8, 0x2c, 0x29, 0x9c, 0x12, 0x40, 0xc8, 0xff, 0x25, 0xc1, 0xac, 0xa8, 0x17, + 0xa9, 0xbc, 0xbf, 0x09, 0x15, 0x6f, 0x44, 0x8f, 0x2d, 0xc8, 0x12, 0xf8, 0x50, 0x2c, 0x92, 0x14, + 0xe3, 0x45, 0x12, 0xc4, 0x0b, 0x35, 0x9c, 0xc5, 0x74, 0x2d, 0xa6, 0x9c, 0xa8, 0xc5, 0x24, 0x62, + 0xf9, 0xcc, 0xa9, 0x62, 0xf9, 0xe5, 0x58, 0x61, 0xa4, 0x42, 0xe5, 0x2c, 0x16, 0x3f, 0xde, 0x40, + 0x43, 0xfc, 0x42, 0x2a, 0xa3, 0x8f, 0x61, 0xce, 0x12, 0xad, 0x81, 0x4b, 0x2a, 0x56, 0x6c, 0x13, + 0x37, 0x29, 0x71, 0xf0, 0xd3, 0x98, 0xc0, 0x53, 0x68, 0x3e, 0x75, 0xed, 0xa1, 0xcd, 0x0b, 0x01, + 0x6f, 0x21, 0xed, 0xfb, 0x02, 0x16, 0x15, 0xac, 0xea, 0xe7, 0xbf, 0xad, 0x0b, 0x2a, 0x5e, 0x8c, + 0xa9, 0xf8, 0x9f, 0xc1, 0x72, 0x8a, 0x42, 0xc8, 0xf4, 0xc7, 0x19, 0x57, 0xf5, 0x2b, 0xa2, 0xe0, + 0x32, 0x98, 0x13, 0x2f, 0xea, 0x9f, 0x40, 0x51, 0x71, 0xb4, 0x2c, 0x45, 0x73, 0xd4, 0x13, 0xd3, + 0x56, 0xc3, 0x74, 0x94, 0x0f, 0x89, 0x28, 0x8e, 0x7c, 0xdf, 0xe9, 0x11, 0xee, 0xb9, 0xa6, 0x91, + 0xf1, 0x23, 0x7c, 0x22, 0x3f, 0x87, 0xca, 0x3e, 0xf6, 0x3c, 0xf2, 0x79, 0x44, 0x1d, 0xa9, 0x52, + 0x30, 0xa4, 0x55, 0x25, 0x18, 0x46, 0x2f, 0x45, 0x05, 0xe1, 0xa5, 0x88, 0x28, 0xe4, 0x48, 0x77, + 0x7a, 0x6c, 0x25, 0x28, 0x81, 0xea, 0xce, 0x01, 0x7d, 0x46, 0xfa, 0x6d, 0x01, 0xe6, 0x62, 0x9f, + 0xf0, 0x16, 0xa5, 0x4b, 0xf8, 0x39, 0x26, 0x16, 0xcd, 0x3d, 0x11, 0x1b, 0x88, 0xa5, 0x93, 0x72, + 0xac, 0x74, 0x82, 0x6e, 0xc0, 0x05, 0x07, 0xbb, 0x43, 0x83, 0x7e, 0x67, 0xcf, 0xc5, 0xaa, 0xce, + 0x13, 0xe5, 0xf9, 0x68, 0x9a, 0xc8, 0x9c, 0x68, 0xa5, 0x00, 0xf8, 0xd2, 0x35, 0x7c, 0x56, 0xa1, + 0x2c, 0x2b, 0x02, 0x82, 0xcf, 0xc8, 0xf4, 0xff, 0x5f, 0xf6, 0x2c, 0xbf, 0x84, 0x46, 0x4c, 0xb2, + 0xdb, 0xda, 0xe0, 0x6d, 0x16, 0x9a, 0x44, 0xb1, 0x97, 0x62, 0x4a, 0xdd, 0x81, 0x85, 0x24, 0x61, + 0x0f, 0xdd, 0x86, 0x92, 0xaa, 0x0d, 0x02, 0x35, 0x5e, 0x15, 0xd5, 0x38, 0x09, 0xac, 0x50, 0x48, + 0xb9, 0x03, 0xf3, 0x71, 0xbb, 0x40, 0x77, 0xa0, 0xc2, 0xb4, 0x3b, 0x40, 0xb3, 0x9c, 0x8b, 0x46, + 0x09, 0x20, 0xe5, 0x2f, 0x12, 0xdc, 0x50, 0xb7, 0x74, 0x16, 0x4c, 0x42, 0x64, 0x2b, 0xc4, 0x22, + 0xdb, 0xef, 0x0a, 0x70, 0xf1, 0x19, 0x95, 0x3b, 0x4f, 0x90, 0x03, 0x03, 0x16, 0xaf, 0x77, 0xd2, + 0xa9, 0xae, 0x77, 0x7f, 0x04, 0xb3, 0xba, 0xe1, 0x39, 0xa6, 0x7a, 0xd2, 0xa3, 0xbb, 0x0b, 0x53, + 0xec, 0xae, 0xf3, 0x1d, 0x7b, 0x2a, 0xd5, 0x1c, 0xb1, 0x72, 0x33, 0x4d, 0xd4, 0x13, 0xea, 0x3a, + 0x1f, 0x0a, 0xd5, 0xa2, 0xd2, 0x14, 0x5b, 0xc3, 0x5a, 0xd2, 0x47, 0x50, 0x35, 0x6d, 0xe6, 0xba, + 0x79, 0xde, 0x34, 0xe1, 0x83, 0x03, 0x68, 0xb2, 0x93, 0xa8, 0xf8, 0xd7, 0xb6, 0x15, 0x44, 0xa5, + 0x09, 0x3b, 0x03, 0x68, 0xf9, 0x5f, 0x0a, 0x80, 0x98, 0xf4, 0xa7, 0xbc, 0xfc, 0x10, 0x55, 0x9c, + 0x5a, 0xa8, 0xac, 0xc0, 0xf6, 0x71, 0xbc, 0xc0, 0x56, 0x9c, 0xea, 0x34, 0x84, 0xf2, 0xdb, 0x99, + 0x05, 0x1a, 0x3f, 0xc6, 0xf2, 0xe9, 0x8e, 0x31, 0x28, 0xcf, 0xcd, 0x4c, 0x57, 0x9e, 0x93, 0x7f, + 0x51, 0x82, 0x12, 0xad, 0x45, 0x25, 0x23, 0x86, 0xf8, 0x82, 0x53, 0x48, 0xbc, 0xe0, 0x5c, 0x4d, + 0x68, 0x6a, 0x91, 0x17, 0x1f, 0x05, 0x5d, 0x9c, 0xf0, 0x36, 0x30, 0xbe, 0x36, 0x19, 0xea, 0x13, + 0xaf, 0x4d, 0x86, 0x1a, 0xd3, 0x12, 0x34, 0x86, 0x97, 0x2c, 0x82, 0x71, 0xac, 0x9c, 0x51, 0x4d, + 0x94, 0x33, 0xae, 0x40, 0x5d, 0x28, 0xce, 0x52, 0x9f, 0x5a, 0x53, 0x20, 0xaa, 0xcd, 0x92, 0x78, + 0xc5, 0x24, 0x45, 0x96, 0x81, 0xed, 0x66, 0x13, 0x5d, 0x1d, 0xbd, 0x0b, 0x73, 0x7d, 0x75, 0x88, + 0x35, 0xfa, 0xba, 0x4a, 0x00, 0xea, 0xac, 0xd1, 0x22, 0x9a, 0x64, 0x29, 0x85, 0xe7, 0x63, 0x95, + 0xf6, 0xd0, 0xcc, 0xf2, 0x5c, 0x8e, 0x8c, 0xbb, 0xf4, 0x3a, 0x69, 0x5b, 0xa6, 0x61, 0xe1, 0xe6, + 0x1c, 0x8d, 0x9d, 0x7c, 0x94, 0x28, 0x8d, 0xce, 0x27, 0x4b, 0xa3, 0x89, 0x28, 0x72, 0xe1, 0x3c, + 0x51, 0xa4, 0x71, 0xaa, 0x28, 0xf2, 0x3b, 0x09, 0xe6, 0xc2, 0x9c, 0x3d, 0xa8, 0x7e, 0x52, 0xbf, + 0x4f, 0xcd, 0x29, 0xb3, 0xfa, 0x19, 0x83, 0x8f, 0x46, 0x0a, 0x8c, 0xc2, 0xe4, 0xbf, 0xf5, 0x6b, + 0x09, 0x6a, 0xe1, 0x0a, 0xba, 0x01, 0x65, 0x8a, 0x8e, 0xbb, 0xc9, 0x85, 0x54, 0x2d, 0x55, 0x61, + 0xeb, 0xdf, 0x77, 0x01, 0xb4, 0x0d, 0x65, 0x9a, 0x47, 0xa2, 0xf7, 0xa0, 0x2c, 0x16, 0x7b, 0xd3, + 0xf5, 0x59, 0xb6, 0x2c, 0x7f, 0x5b, 0x80, 0x35, 0x1a, 0xf8, 0xcf, 0xf9, 0xb6, 0x84, 0xfe, 0x14, + 0x66, 0x58, 0x45, 0x89, 0xbb, 0xa9, 0x7b, 0x22, 0xc5, 0xb1, 0x14, 0xd2, 0xe5, 0x26, 0x0a, 0xae, + 0x70, 0x7c, 0xad, 0x17, 0xb0, 0x94, 0x0d, 0x11, 0x15, 0x15, 0xa5, 0xbc, 0xa2, 0x62, 0x21, 0x51, + 0x54, 0x14, 0x0d, 0xad, 0x18, 0x37, 0x34, 0xf9, 0x2f, 0x0a, 0x80, 0x28, 0xde, 0xf3, 0xe6, 0x77, + 0x61, 0x1a, 0x57, 0xcc, 0x49, 0xe3, 0x4a, 0xf1, 0xc4, 0x64, 0x37, 0x9d, 0xc6, 0x4d, 0x71, 0x89, + 0x4f, 0xe6, 0x78, 0x0f, 0x32, 0x72, 0xbc, 0x99, 0xc9, 0x68, 0x92, 0x09, 0xa0, 0xfc, 0x1c, 0x5a, + 0x69, 0x29, 0x78, 0x51, 0x8a, 0x90, 0x48, 0x44, 0x2e, 0xa7, 0xce, 0x39, 0x3b, 0x1b, 0xd9, 0xfa, + 0xd7, 0x5b, 0x30, 0xb3, 0x47, 0x41, 0xd1, 0x67, 0x00, 0x51, 0x2f, 0x16, 0x8a, 0x77, 0x84, 0x24, + 0x7b, 0xb4, 0x5a, 0x4b, 0x29, 0xee, 0x3b, 0x43, 0xc7, 0x3f, 0x91, 0xd1, 0x4f, 0xfe, 0xfd, 0xbf, + 0xff, 0xb2, 0x30, 0x2b, 0x43, 0xfb, 0x78, 0xab, 0xcd, 0x1e, 0xc3, 0x90, 0x05, 0x73, 0xb1, 0xfe, + 0x2c, 0xb4, 0x9e, 0xc0, 0x9d, 0xba, 0x69, 0xe5, 0xa2, 0x7f, 0x97, 0xa2, 0x5f, 0x93, 0x9b, 0x04, + 0x3d, 0xb5, 0xe5, 0xf6, 0xeb, 0x20, 0x40, 0xbf, 0x69, 0xab, 0xba, 0x7e, 0x57, 0xda, 0x40, 0x3f, + 0x91, 0x00, 0xa5, 0x3b, 0xb8, 0xd0, 0xf5, 0x18, 0xd5, 0xbc, 0x0e, 0xaf, 0xd6, 0x62, 0x2c, 0x87, + 0x63, 0xb7, 0x15, 0xf9, 0x36, 0xa5, 0xbb, 0x21, 0x5f, 0x21, 0x74, 0x79, 0xbd, 0xb2, 0xad, 0x0a, + 0x38, 0xda, 0xac, 0xbd, 0xf1, 0x6e, 0x58, 0xcc, 0x4c, 0x32, 0xc1, 0xbb, 0x11, 0x73, 0x99, 0x88, + 0xf5, 0x70, 0x9d, 0x95, 0x09, 0xd6, 0x7f, 0x19, 0x31, 0xf1, 0x0d, 0x2c, 0xa4, 0x9a, 0xb0, 0xd0, + 0xb5, 0x3c, 0x16, 0xc4, 0x1e, 0xad, 0x6c, 0x0e, 0xda, 0x94, 0x83, 0xf7, 0xe5, 0xcb, 0xb9, 0x1c, + 0xd0, 0xee, 0xc9, 0x88, 0x81, 0x6f, 0x25, 0xb8, 0x98, 0xd5, 0x65, 0x85, 0x6e, 0xe4, 0x31, 0x91, + 0xa8, 0x25, 0x67, 0xf3, 0xb1, 0x45, 0xf9, 0xf8, 0x40, 0xbe, 0x9a, 0xcb, 0x47, 0x10, 0x89, 0x23, + 0x56, 0x7e, 0x26, 0xc1, 0x52, 0x76, 0x57, 0x14, 0x7a, 0x3f, 0x8f, 0x99, 0x54, 0xe7, 0x54, 0x36, + 0x3b, 0x3f, 0xa4, 0xec, 0x6c, 0xca, 0xef, 0xe6, 0xb2, 0x13, 0x05, 0xf6, 0x7c, 0x0d, 0xe1, 0xed, + 0x94, 0xb9, 0x1a, 0x12, 0xeb, 0x8d, 0x3a, 0xab, 0x86, 0x30, 0xb3, 0xca, 0xd5, 0x10, 0xd6, 0xab, + 0x99, 0xab, 0x21, 0x62, 0xef, 0xd3, 0x59, 0x35, 0x84, 0xe6, 0x30, 0x11, 0x03, 0x2a, 0xcc, 0x8a, + 0xdd, 0x4b, 0x28, 0x56, 0x9a, 0xc8, 0xe8, 0x6b, 0xca, 0x75, 0x0d, 0x4d, 0x4a, 0x19, 0xc9, 0x8d, + 0xc8, 0xf3, 0xb4, 0x0f, 0xc9, 0x7e, 0xf4, 0x1c, 0xea, 0x42, 0xf3, 0x0a, 0x8a, 0xf9, 0xc6, 0x74, + 0x57, 0x4b, 0x2b, 0x9d, 0x36, 0xc8, 0x17, 0x29, 0xee, 0x79, 0xb9, 0x16, 0xba, 0x1d, 0xe2, 0x67, + 0x7e, 0x0c, 0x73, 0xb1, 0xd6, 0x93, 0xb8, 0x5f, 0xcb, 0xea, 0x4a, 0x99, 0xe4, 0x36, 0x37, 0x44, + 0xb7, 0xa9, 0x41, 0x5d, 0xe8, 0x48, 0x89, 0xb3, 0x9d, 0x6e, 0x55, 0xc9, 0x45, 0xbd, 0x4a, 0x51, + 0x2f, 0x6d, 0x5c, 0xcc, 0x72, 0x99, 0xe8, 0xa7, 0x12, 0xbc, 0x93, 0xd3, 0xc6, 0x82, 0x36, 0xd2, + 0x14, 0xf3, 0xb2, 0x85, 0x5c, 0xea, 0x37, 0x29, 0x75, 0x79, 0x63, 0x9d, 0x50, 0x17, 0x72, 0x93, + 0xf6, 0xeb, 0x78, 0xfa, 0xf2, 0x06, 0xd9, 0xb0, 0x98, 0xd1, 0x06, 0x83, 0xde, 0x4b, 0x33, 0x91, + 0x55, 0x18, 0x9f, 0xa4, 0x16, 0x1b, 0x54, 0x2d, 0xc4, 0xaa, 0x20, 0x3a, 0x0e, 0x3a, 0x87, 0x12, + 0xf5, 0x81, 0x1b, 0x13, 0xfa, 0x58, 0x26, 0x92, 0x5c, 0xa3, 0x24, 0xdf, 0x69, 0x21, 0x42, 0xd2, + 0x63, 0x5b, 0xdb, 0x3a, 0x45, 0x44, 0xd4, 0x66, 0x0f, 0xe0, 0x21, 0xf6, 0x83, 0x9e, 0xfd, 0x1c, + 0x24, 0x71, 0xeb, 0xe2, 0xc0, 0xf2, 0x22, 0xc5, 0x3c, 0x87, 0xea, 0x82, 0x75, 0xa1, 0xc7, 0x50, + 0x0d, 0x5a, 0x4d, 0x50, 0xec, 0xa1, 0x2d, 0xd1, 0x80, 0x12, 0x57, 0x6c, 0xba, 0x22, 0x37, 0x28, + 0x42, 0x40, 0x55, 0x82, 0x90, 0xf6, 0x06, 0xec, 0x43, 0xfd, 0x8f, 0xb1, 0x6a, 0xfa, 0x47, 0xda, + 0x11, 0xd6, 0x06, 0xb9, 0xec, 0xe5, 0x7d, 0x3b, 0xb7, 0x14, 0x34, 0xdb, 0x3e, 0x12, 0xb0, 0x7c, + 0x03, 0x97, 0x32, 0x1b, 0x00, 0xd0, 0x4d, 0x91, 0xa5, 0x71, 0x3d, 0x02, 0xb9, 0x04, 0xaf, 0x51, + 0x82, 0x97, 0xe5, 0x45, 0xc1, 0xec, 0xd3, 0xce, 0xbf, 0x0f, 0xb5, 0xf0, 0xe1, 0x1f, 0xc5, 0xca, + 0x46, 0xc9, 0x7e, 0x80, 0x89, 0x84, 0x96, 0x33, 0x53, 0x8f, 0x2f, 0x6d, 0xc3, 0x22, 0x87, 0xfb, + 0x15, 0xcc, 0xc7, 0x5b, 0x06, 0x50, 0xac, 0x2d, 0x2a, 0xb3, 0x9d, 0xe0, 0x8c, 0x24, 0x07, 0x86, + 0x36, 0x20, 0x24, 0xbf, 0x04, 0x88, 0x5e, 0x6d, 0x51, 0xf2, 0xc9, 0xf8, 0x78, 0x3a, 0x2f, 0x71, + 0x9d, 0x92, 0xba, 0x22, 0xb7, 0x32, 0x49, 0x99, 0x04, 0x0f, 0xa1, 0xa5, 0x01, 0x3c, 0x36, 0xac, + 0x01, 0xcf, 0xa8, 0xf2, 0x7b, 0xdf, 0x73, 0xe9, 0xc8, 0x94, 0xce, 0xaa, 0xfc, 0x8e, 0x18, 0x1f, + 0x4c, 0xc3, 0x1a, 0x04, 0x09, 0x54, 0x44, 0x84, 0x67, 0x4c, 0xf9, 0xdd, 0xeb, 0x67, 0x20, 0xc2, + 0x13, 0x24, 0x69, 0x03, 0x7d, 0x01, 0x35, 0x42, 0x84, 0xa5, 0x44, 0xb9, 0xfd, 0xe7, 0xb9, 0x24, + 0xae, 0x52, 0x12, 0x2b, 0xf2, 0x52, 0x8a, 0x04, 0xcb, 0x80, 0xa4, 0x0d, 0xe4, 0xc1, 0xac, 0xf8, + 0x38, 0x1e, 0x8f, 0x6c, 0x19, 0xcf, 0xe6, 0xb9, 0xb4, 0x36, 0x28, 0xad, 0x6b, 0x4c, 0x0d, 0x62, + 0xb4, 0xd2, 0x8a, 0x6e, 0xc3, 0x3c, 0x41, 0x2d, 0x24, 0x37, 0xe3, 0x5b, 0xbb, 0x73, 0x89, 0xbe, + 0x47, 0x89, 0xae, 0xcb, 0x2b, 0x29, 0xa2, 0x42, 0x2e, 0x13, 0x1d, 0x16, 0x4f, 0x5e, 0xf2, 0x1b, + 0xb4, 0xcf, 0x70, 0x58, 0x3c, 0x57, 0x89, 0x0e, 0x8b, 0x65, 0x27, 0xb9, 0x2d, 0xd6, 0x67, 0x38, + 0x2c, 0x96, 0x8c, 0x48, 0x1b, 0xe8, 0x1b, 0x58, 0xcc, 0x68, 0x23, 0x88, 0x47, 0x9f, 0xfc, 0x3e, + 0x83, 0xd6, 0xe5, 0xfc, 0x46, 0x5c, 0xb2, 0x4b, 0x5e, 0xa7, 0x1c, 0xb4, 0x10, 0xbd, 0xb7, 0xf0, + 0xfe, 0x83, 0xf6, 0xeb, 0xa8, 0x35, 0xe1, 0x0d, 0xda, 0x83, 0x3a, 0x81, 0x0c, 0x1c, 0xe3, 0x54, + 0x61, 0x81, 0x03, 0x07, 0xd9, 0x03, 0x12, 0xb3, 0x87, 0xaf, 0x88, 0x22, 0x88, 0x9d, 0x05, 0x71, + 0x47, 0x94, 0xd9, 0x75, 0xd0, 0x5a, 0xce, 0xed, 0x3e, 0x0b, 0x64, 0x88, 0xb2, 0x7d, 0x11, 0x0d, + 0x1d, 0x3f, 0x97, 0x60, 0x29, 0xbb, 0x31, 0x21, 0x9e, 0x61, 0x8f, 0x6d, 0x5e, 0x68, 0x5d, 0x1d, + 0xdb, 0xf3, 0x42, 0x79, 0xe1, 0x49, 0x05, 0x9a, 0x9c, 0x54, 0x7c, 0xc6, 0xa4, 0xca, 0x3b, 0x0f, + 0xe2, 0x39, 0x54, 0xba, 0x25, 0xa1, 0x75, 0x29, 0xf5, 0xf4, 0x4a, 0xe9, 0x2d, 0x50, 0x7a, 0x75, + 0x44, 0xd3, 0x3f, 0xfa, 0x14, 0x8b, 0xbe, 0x82, 0x85, 0xd4, 0x1b, 0x7d, 0x3c, 0x6f, 0xce, 0x7b, + 0xc2, 0x6f, 0xad, 0xe6, 0xbd, 0x5a, 0x52, 0x5a, 0x3c, 0x5f, 0x41, 0xe9, 0x7c, 0xe5, 0x57, 0x12, + 0x6b, 0xa3, 0x48, 0xa4, 0x2b, 0xd7, 0x93, 0x44, 0xb3, 0x93, 0x95, 0xb5, 0xdc, 0xa7, 0x09, 0x4a, + 0xf6, 0x01, 0x25, 0x7b, 0x8f, 0x29, 0x68, 0x90, 0xb3, 0xbc, 0x8e, 0x0a, 0x2a, 0x6f, 0x3e, 0xbf, + 0x86, 0xe4, 0xbc, 0xb5, 0xf6, 0x6b, 0xfe, 0x84, 0xf3, 0x06, 0x39, 0x4c, 0xed, 0xa2, 0xf7, 0xf9, + 0xb4, 0xda, 0xa5, 0xde, 0xee, 0xe3, 0x6a, 0x17, 0x2b, 0xfb, 0xc5, 0x0d, 0x87, 0x50, 0x89, 0x68, + 0x31, 0x2d, 0x44, 0x5f, 0xc3, 0x42, 0xea, 0xc1, 0x36, 0x7e, 0x12, 0x79, 0xef, 0xb9, 0xb9, 0xfe, + 0xe2, 0x06, 0x25, 0x7a, 0x55, 0x5e, 0xcd, 0xd4, 0x75, 0x87, 0xa1, 0x23, 0x5e, 0xc3, 0x06, 0x94, + 0x7e, 0x78, 0x8d, 0x9f, 0x48, 0xee, 0xc3, 0x6c, 0xab, 0x95, 0x7b, 0x22, 0x9e, 0xbc, 0x44, 0x39, + 0x68, 0xc8, 0x75, 0x41, 0xe4, 0x84, 0xe0, 0xe7, 0x50, 0x51, 0x1c, 0xed, 0xc1, 0xc8, 0xd2, 0xd0, + 0x85, 0x18, 0x15, 0x47, 0x6b, 0x25, 0x27, 0xe4, 0x5b, 0x14, 0xc9, 0x0d, 0x79, 0x96, 0x20, 0x71, + 0x1d, 0xad, 0xfd, 0xda, 0xd0, 0xdf, 0xdc, 0x0d, 0xde, 0x69, 0x3f, 0x27, 0x49, 0x9a, 0xb0, 0x80, + 0xfa, 0x30, 0xfb, 0xcc, 0x32, 0xcf, 0x15, 0xdd, 0x63, 0x09, 0x4b, 0xe0, 0x68, 0x47, 0x56, 0x22, + 0xbe, 0x87, 0x84, 0xce, 0x1e, 0xe1, 0xc7, 0x11, 0x8a, 0x62, 0xbc, 0x0e, 0x75, 0x46, 0xe8, 0xac, + 0x51, 0x3e, 0x56, 0x6e, 0x4a, 0x90, 0x09, 0xe3, 0xfc, 0x10, 0xe6, 0x19, 0x95, 0x30, 0xd2, 0x8f, + 0x6b, 0x77, 0x3b, 0x5d, 0xc0, 0xe5, 0xb4, 0xc2, 0x38, 0x4f, 0xd3, 0x8a, 0x06, 0x23, 0x77, 0xfe, + 0x18, 0xcf, 0xfd, 0xa8, 0xbc, 0x96, 0x41, 0x32, 0x1e, 0xe5, 0xc3, 0x23, 0x3b, 0x7b, 0x9c, 0x1f, + 0x77, 0x64, 0x51, 0xa4, 0x0f, 0x8f, 0xec, 0xac, 0xb1, 0x7e, 0xdc, 0x91, 0x85, 0xd1, 0x5e, 0x85, + 0xb9, 0xd8, 0x53, 0x6b, 0xfc, 0xe6, 0x9e, 0xf5, 0x0a, 0x9b, 0x4b, 0x8f, 0x5b, 0x6a, 0x4b, 0xbc, + 0x92, 0x11, 0x12, 0x2f, 0xa0, 0x2e, 0xbc, 0x27, 0xc6, 0x23, 0x4f, 0xfa, 0xa1, 0x31, 0x17, 0xfd, + 0x15, 0x8a, 0x7e, 0xb9, 0x95, 0x79, 0x7b, 0x27, 0x74, 0xfe, 0x4a, 0x82, 0xa5, 0xec, 0x42, 0x7e, + 0x3c, 0xe8, 0x8e, 0x2d, 0xf6, 0xb7, 0xc6, 0x37, 0x9a, 0x86, 0x75, 0xa5, 0x89, 0x01, 0xf7, 0x2e, + 0x7f, 0x1e, 0x40, 0x3e, 0x2c, 0x66, 0xd4, 0xab, 0xe3, 0xf9, 0x54, 0x7e, 0x41, 0x7b, 0x4c, 0xb4, + 0xda, 0xd6, 0x06, 0x5e, 0x5c, 0xe8, 0x91, 0x7b, 0xbc, 0xff, 0x37, 0x85, 0x5f, 0x6c, 0xff, 0xa7, + 0x84, 0x46, 0x30, 0xc7, 0x6a, 0xda, 0xeb, 0xdb, 0x4f, 0xbb, 0xeb, 0xc7, 0x5b, 0x72, 0x0f, 0xae, + 0x1e, 0x1c, 0xe1, 0xf5, 0x60, 0x92, 0x36, 0x75, 0x79, 0xeb, 0xef, 0xad, 0xef, 0xd8, 0x96, 0xef, + 0x1a, 0x87, 0x23, 0xdf, 0x26, 0x31, 0xe3, 0xc8, 0xf7, 0x1d, 0xef, 0x6e, 0xbb, 0xdd, 0x37, 0xfc, + 0xa3, 0xd1, 0xe1, 0xa6, 0x66, 0x0f, 0xdb, 0x47, 0xd8, 0xb5, 0x0d, 0xcd, 0x54, 0x0f, 0xbd, 0x36, + 0x63, 0xa8, 0x75, 0xf1, 0x08, 0x9b, 0xa6, 0x7d, 0x2f, 0x5a, 0x20, 0x70, 0x5b, 0xc5, 0xad, 0xcd, + 0xdb, 0x1b, 0x92, 0xb4, 0xd5, 0x50, 0x1d, 0xc7, 0xe4, 0x01, 0xbb, 0xfd, 0xa5, 0x67, 0x5b, 0x77, + 0x53, 0x33, 0xee, 0x5d, 0x58, 0xe1, 0x8c, 0x78, 0xd8, 0x3d, 0xc6, 0xee, 0xba, 0x6e, 0x6b, 0xa3, + 0x21, 0xb6, 0xd8, 0xdf, 0xf5, 0xa3, 0x95, 0x80, 0x8d, 0x38, 0x89, 0xb6, 0x6e, 0x6b, 0x1e, 0x2c, + 0x6b, 0xf6, 0x70, 0x53, 0x58, 0x88, 0xa4, 0x74, 0xbf, 0xc6, 0x90, 0x6e, 0x3b, 0xc6, 0x53, 0xe9, + 0xf3, 0xa2, 0xea, 0x18, 0xbf, 0x2a, 0x94, 0xf6, 0x1e, 0x3d, 0xbd, 0xff, 0xf7, 0x05, 0x5e, 0xe0, + 0x3f, 0x9c, 0xa1, 0xba, 0x75, 0xe7, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xbc, 0x66, 0x2e, + 0xf8, 0x40, 0x00, 0x00, } diff --git a/api/api.pb.gw.go b/api/api.pb.gw.go index a5da1610f..bb78399b1 100644 --- a/api/api.pb.gw.go +++ b/api/api.pb.gw.go @@ -46,6 +46,39 @@ func request_Nakama_AddFriends_0(ctx context.Context, marshaler runtime.Marshale } +func request_Nakama_AddGroupUsers_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddGroupUsersRequest + var metadata runtime.ServerMetadata + + if req.ContentLength > 0 { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.AddGroupUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + var ( filter_Nakama_AuthenticateCustom_0 = &utilities.DoubleArray{Encoding: map[string]int{"account": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -225,7 +258,7 @@ func request_Nakama_BlockFriends_0(ctx context.Context, marshaler runtime.Marsha } func request_Nakama_CreateGroup_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq CreateGroupsRequest + var protoReq CreateGroupRequest var metadata runtime.ServerMetadata if req.ContentLength > 0 { @@ -256,6 +289,33 @@ func request_Nakama_DeleteFriends_0(ctx context.Context, marshaler runtime.Marsh } +func request_Nakama_DeleteGroup_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteGroupRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.DeleteGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_Nakama_DeleteLeaderboardRecord_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq DeleteLeaderboardRecordRequest var metadata runtime.ServerMetadata @@ -373,6 +433,105 @@ func request_Nakama_ImportFacebookFriends_0(ctx context.Context, marshaler runti } +func request_Nakama_JoinGroup_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq JoinGroupRequest + var metadata runtime.ServerMetadata + + if req.ContentLength > 0 { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.JoinGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_Nakama_KickGroupUsers_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq KickGroupUsersRequest + var metadata runtime.ServerMetadata + + if req.ContentLength > 0 { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.KickGroupUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_Nakama_LeaveGroup_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq LeaveGroupRequest + var metadata runtime.ServerMetadata + + if req.ContentLength > 0 { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.LeaveGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_Nakama_LinkCustom_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AccountCustom var metadata runtime.ServerMetadata @@ -487,13 +646,31 @@ func request_Nakama_LinkSteam_0(ctx context.Context, marshaler runtime.Marshaler } var ( - filter_Nakama_ListChannelMessages_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_Nakama_ListChannelMessages_0 = &utilities.DoubleArray{Encoding: map[string]int{"channel_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) func request_Nakama_ListChannelMessages_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ListChannelMessagesRequest var metadata runtime.ServerMetadata + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_Nakama_ListChannelMessages_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -512,6 +689,33 @@ func request_Nakama_ListFriends_0(ctx context.Context, marshaler runtime.Marshal } +func request_Nakama_ListGroupUsers_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListGroupUsersRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.ListGroupUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + var ( filter_Nakama_ListLeaderboardRecords_0 = &utilities.DoubleArray{Encoding: map[string]int{"leaderboard_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -662,6 +866,66 @@ func request_Nakama_ListStorageObjects_1(ctx context.Context, marshaler runtime. } +func request_Nakama_ListUserGroups_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListUserGroupsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["user_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user_id") + } + + protoReq.UserId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user_id", err) + } + + msg, err := client.ListUserGroups(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_Nakama_PromoteGroupUsers_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PromoteGroupUsersRequest + var metadata runtime.ServerMetadata + + if req.ContentLength > 0 { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.PromoteGroupUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_Nakama_ReadStorageObjects_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ReadStorageObjectsRequest var metadata runtime.ServerMetadata @@ -873,6 +1137,39 @@ func request_Nakama_UpdateAccount_0(ctx context.Context, marshaler runtime.Marsh } +func request_Nakama_UpdateGroup_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateGroupRequest + var metadata runtime.ServerMetadata + + if req.ContentLength > 0 { + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["group_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "group_id") + } + + protoReq.GroupId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "group_id", err) + } + + msg, err := client.UpdateGroup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_Nakama_WriteLeaderboardRecord_0(ctx context.Context, marshaler runtime.Marshaler, client NakamaClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq WriteLeaderboardRecordRequest var metadata runtime.ServerMetadata @@ -988,6 +1285,35 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("POST", pattern_Nakama_AddGroupUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_AddGroupUsers_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_AddGroupUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_Nakama_AuthenticateCustom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1278,6 +1604,35 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("DELETE", pattern_Nakama_DeleteGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_DeleteGroup_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_DeleteGroup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("DELETE", pattern_Nakama_DeleteLeaderboardRecord_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1481,6 +1836,93 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("POST", pattern_Nakama_JoinGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_JoinGroup_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_JoinGroup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Nakama_KickGroupUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_KickGroupUsers_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_KickGroupUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Nakama_LeaveGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_LeaveGroup_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_LeaveGroup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_Nakama_LinkCustom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1742,6 +2184,35 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("GET", pattern_Nakama_ListGroupUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_ListGroupUsers_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_ListGroupUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Nakama_ListLeaderboardRecords_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1887,6 +2358,64 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("GET", pattern_Nakama_ListUserGroups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_ListUserGroups_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_ListUserGroups_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Nakama_PromoteGroupUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_PromoteGroupUsers_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_PromoteGroupUsers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_Nakama_ReadStorageObjects_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2206,6 +2735,35 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("PUT", pattern_Nakama_UpdateGroup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Nakama_UpdateGroup_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Nakama_UpdateGroup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_Nakama_WriteLeaderboardRecord_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2270,6 +2828,8 @@ func RegisterNakamaHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli var ( pattern_Nakama_AddFriends_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "friend"}, "")) + pattern_Nakama_AddGroupUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "group", "group_id", "add"}, "")) + pattern_Nakama_AuthenticateCustom_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "account", "authenticate", "custom"}, "")) pattern_Nakama_AuthenticateDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "account", "authenticate", "device"}, "")) @@ -2290,6 +2850,8 @@ var ( pattern_Nakama_DeleteFriends_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "friend"}, "")) + pattern_Nakama_DeleteGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "group", "group_id"}, "")) + pattern_Nakama_DeleteLeaderboardRecord_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "leaderboard", "leaderboard_id"}, "")) pattern_Nakama_DeleteNotifications_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "notification"}, "")) @@ -2304,6 +2866,12 @@ var ( pattern_Nakama_ImportFacebookFriends_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "friend", "facebook"}, "")) + pattern_Nakama_JoinGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "group", "group_id", "join"}, "")) + + pattern_Nakama_KickGroupUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "group", "group_id", "kick"}, "")) + + pattern_Nakama_LeaveGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "group", "group_id", "leave"}, "")) + pattern_Nakama_LinkCustom_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "account", "link", "custom"}, "")) pattern_Nakama_LinkDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "account", "link", "device"}, "")) @@ -2318,10 +2886,12 @@ var ( pattern_Nakama_LinkSteam_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "account", "link", "steam"}, "")) - pattern_Nakama_ListChannelMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "channel"}, "")) + pattern_Nakama_ListChannelMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "channel", "channel_id"}, "")) pattern_Nakama_ListFriends_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "friend"}, "")) + pattern_Nakama_ListGroupUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "group", "group_id", "user"}, "")) + pattern_Nakama_ListLeaderboardRecords_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "leaderboard", "leaderboard_id"}, "")) pattern_Nakama_ListMatches_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "match"}, "")) @@ -2332,6 +2902,10 @@ var ( pattern_Nakama_ListStorageObjects_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "storage", "collection", "user_id"}, "")) + pattern_Nakama_ListUserGroups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "user", "user_id", "group"}, "")) + + pattern_Nakama_PromoteGroupUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v2", "group", "group_id", "promote"}, "")) + pattern_Nakama_ReadStorageObjects_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "storage"}, "")) pattern_Nakama_RpcFunc_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "rpc", "id"}, "")) @@ -2354,6 +2928,8 @@ var ( pattern_Nakama_UpdateAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "account"}, "")) + pattern_Nakama_UpdateGroup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "group", "group_id"}, "")) + pattern_Nakama_WriteLeaderboardRecord_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v2", "leaderboard", "leaderboard_id"}, "")) pattern_Nakama_WriteStorageObjects_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v2", "storage"}, "")) @@ -2362,6 +2938,8 @@ var ( var ( forward_Nakama_AddFriends_0 = runtime.ForwardResponseMessage + forward_Nakama_AddGroupUsers_0 = runtime.ForwardResponseMessage + forward_Nakama_AuthenticateCustom_0 = runtime.ForwardResponseMessage forward_Nakama_AuthenticateDevice_0 = runtime.ForwardResponseMessage @@ -2382,6 +2960,8 @@ var ( forward_Nakama_DeleteFriends_0 = runtime.ForwardResponseMessage + forward_Nakama_DeleteGroup_0 = runtime.ForwardResponseMessage + forward_Nakama_DeleteLeaderboardRecord_0 = runtime.ForwardResponseMessage forward_Nakama_DeleteNotifications_0 = runtime.ForwardResponseMessage @@ -2396,6 +2976,12 @@ var ( forward_Nakama_ImportFacebookFriends_0 = runtime.ForwardResponseMessage + forward_Nakama_JoinGroup_0 = runtime.ForwardResponseMessage + + forward_Nakama_KickGroupUsers_0 = runtime.ForwardResponseMessage + + forward_Nakama_LeaveGroup_0 = runtime.ForwardResponseMessage + forward_Nakama_LinkCustom_0 = runtime.ForwardResponseMessage forward_Nakama_LinkDevice_0 = runtime.ForwardResponseMessage @@ -2414,6 +3000,8 @@ var ( forward_Nakama_ListFriends_0 = runtime.ForwardResponseMessage + forward_Nakama_ListGroupUsers_0 = runtime.ForwardResponseMessage + forward_Nakama_ListLeaderboardRecords_0 = runtime.ForwardResponseMessage forward_Nakama_ListMatches_0 = runtime.ForwardResponseMessage @@ -2424,6 +3012,10 @@ var ( forward_Nakama_ListStorageObjects_1 = runtime.ForwardResponseMessage + forward_Nakama_ListUserGroups_0 = runtime.ForwardResponseMessage + + forward_Nakama_PromoteGroupUsers_0 = runtime.ForwardResponseMessage + forward_Nakama_ReadStorageObjects_0 = runtime.ForwardResponseMessage forward_Nakama_RpcFunc_0 = runtime.ForwardResponseMessage @@ -2446,6 +3038,8 @@ var ( forward_Nakama_UpdateAccount_0 = runtime.ForwardResponseMessage + forward_Nakama_UpdateGroup_0 = runtime.ForwardResponseMessage + forward_Nakama_WriteLeaderboardRecord_0 = runtime.ForwardResponseMessage forward_Nakama_WriteStorageObjects_0 = runtime.ForwardResponseMessage diff --git a/api/api.proto b/api/api.proto index cf08c4bed..2fecde8b1 100644 --- a/api/api.proto +++ b/api/api.proto @@ -90,6 +90,14 @@ service Nakama { option (google.api.http).post = "/v2/friend"; } + // Add users to a group. + rpc AddGroupUsers (AddGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/group/{group_id}/add", + body: "*" + }; + } + // Authenticate a user with a custom id against the server. rpc AuthenticateCustom (AuthenticateCustomRequest) returns (Session) { option (google.api.http) = { @@ -153,8 +161,8 @@ service Nakama { }; } - // Create one or more new groups with the current user as the owner. - rpc CreateGroup (CreateGroupsRequest) returns (Groups) { + // Create a new group with the current user as the owner. + rpc CreateGroup (CreateGroupRequest) returns (Group) { option (google.api.http) = { post: "/v2/group", body: "*" @@ -166,6 +174,11 @@ service Nakama { option (google.api.http).delete = "/v2/friend"; } + // Delete one or more groups by ID. + rpc DeleteGroup (DeleteGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http).delete = "/v2/group/{group_id}"; + } + // Delete a leaderboard record. rpc DeleteLeaderboardRecord (DeleteLeaderboardRecordRequest) returns (google.protobuf.Empty) { option (google.api.http).delete = "/v2/leaderboard/{leaderboard_id}"; @@ -207,6 +220,30 @@ service Nakama { }; } + // Immediately join an open group, or request to join a closed one. + rpc JoinGroup (JoinGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/group/{group_id}/join", + body: "*" + }; + } + + // Kick a set of users from a group. + rpc KickGroupUsers (KickGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/group/{group_id}/kick", + body: "*" + }; + } + + // Leave a group the user is a member of. + rpc LeaveGroup (LeaveGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/group/{group_id}/leave", + body: "*" + }; + } + // Add a custom ID to the social profiles on the current user's account. rpc LinkCustom (AccountCustom) returns (google.protobuf.Empty) { option (google.api.http) = { @@ -265,7 +302,7 @@ service Nakama { // List a channel's message history. rpc ListChannelMessages (ListChannelMessagesRequest) returns (ChannelMessageList) { - option (google.api.http).get = "/v2/channel"; + option (google.api.http).get = "/v2/channel/{channel_id}"; } // List all friends for the current user. @@ -273,6 +310,11 @@ service Nakama { option (google.api.http).get = "/v2/friend"; } + // List all users that are part of a group. + rpc ListGroupUsers (ListGroupUsersRequest) returns (GroupUserList) { + option (google.api.http).get = "/v2/group/{group_id}/user"; + } + // List leaderboard records rpc ListLeaderboardRecords (ListLeaderboardRecordsRequest) returns (LeaderboardRecordList) { option (google.api.http).get = "/v2/leaderboard/{leaderboard_id}"; @@ -298,6 +340,19 @@ service Nakama { }; } + // List groups the current user belongs to. + rpc ListUserGroups (ListUserGroupsRequest) returns (UserGroupList) { + option (google.api.http).get = "/v2/user/{user_id}/group"; + } + + // Promote a set of users in a group to the next role up. + rpc PromoteGroupUsers (PromoteGroupUsersRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/group/{group_id}/promote", + body: "*" + }; + } + // Get storage objects. rpc ReadStorageObjects (ReadStorageObjectsRequest) returns (StorageObjects) { option (google.api.http) = { @@ -381,6 +436,14 @@ service Nakama { }; } + // Update fields in a given group. + rpc UpdateGroup (UpdateGroupRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/group/{group_id}", + body: "*" + }; + } + // Write a record to a leaderboard. rpc WriteLeaderboardRecord (WriteLeaderboardRecordRequest) returns (LeaderboardRecord) { option (google.api.http) = { @@ -478,6 +541,14 @@ message AddFriendsRequest { repeated string usernames = 2; } +// Add users to a group. +message AddGroupUsersRequest { + // The group to add users to. + string group_id = 1; + // The users to add. + repeated string user_ids = 2; +} + // Authenticate against the server with a custom ID. message AuthenticateCustomRequest { // The custom account details. @@ -590,26 +661,18 @@ message ChannelMessageList { string prev_cursor = 3; } -// Create one or more groups with the current user as owner. -message CreateGroupsRequest { - // A group to create. - message NewGroup { - // A unique name for the group. - string name = 1; - // A description for the group. - string description = 2; - // The language expected to be a tag which follows the BCP-47 spec. - string lang_tag = 3; - // Additional information stored as a JSON object. - string metadata = 4; - // A URL for an avatar image. - string avatar_url = 5; - // Mark a group as private where only admins can accept members. - bool private = 6; - } - - // The Group objects to create. - repeated NewGroup groups = 1; +// Create a group with the current user as owner. +message CreateGroupRequest { + // A unique name for the group. + string name = 1; + // A description for the group. + string description = 2; + // The language expected to be a tag which follows the BCP-47 spec. + string lang_tag = 3; + // A URL for an avatar image. + string avatar_url = 4; + // Mark a group as open or not where only admins can accept members. + bool open = 5; } // Delete one or more friends for the current user. @@ -620,6 +683,12 @@ message DeleteFriendsRequest { repeated string usernames = 2; } +// Delete a group the user has access to. +message DeleteGroupRequest { + // The id of a group. + string group_id = 1; +} + // Delete a leaderboard record. message DeleteLeaderboardRecordRequest { // The leaderboard ID to delete from. @@ -702,20 +771,44 @@ message Group { string metadata = 6; // A URL for an avatar image. string avatar_url = 7; - // Mark a group as private where only admins can accept members. - bool private = 8; + // Anyone can join open groups, otherwise only admins can accept members. + google.protobuf.BoolValue open = 8; // The current count of all members in the group. - int32 count = 9; + int32 edge_count = 9; + // The maximum number of members allowed. + int32 max_count = 10; // The UNIX time when the group was created. - google.protobuf.Timestamp create_time = 10; + google.protobuf.Timestamp create_time = 11; // The UNIX time when the group was last updated. - google.protobuf.Timestamp update_time = 11; + google.protobuf.Timestamp update_time = 12; } -// A collection of zero or more groups. -message Groups { - // The Group objects. - repeated Group groups = 1; +// A list of users belonging to a group, along with their role. +message GroupUserList { + // A single user-role pair. + message GroupUser { + // The group role status. + enum State { + // Default case. Assumed as SUPERADMIN state. + STATE_UNSPECIFIED = 0; + // The user is a superadmin with full control of the group. + SUPERADMIN = 1; + // The user is an admin with additional privileges. + ADMIN = 2; + // The user is a regular member. + MEMBER = 3; + // The user has requested to join the group + JOIN_REQUEST = 4; + } + + // User. + User user = 1; + // Their relationship to the group. + int32 state = 2; + } + + // User-role pairs for a group. + repeated GroupUser group_users = 1; } // Import Facebook friends into the current user's account. @@ -726,6 +819,20 @@ message ImportFacebookFriendsRequest { google.protobuf.BoolValue reset = 2; } +// Immediately join an open group, or request to join a closed one. +message JoinGroupRequest { + // The group ID to join. + string group_id = 1; +} + +// Kick a set of users from a group. +message KickGroupUsersRequest { + // The group ID to kick from. + string group_id = 1; + // The users to kick. + repeated string user_ids = 2; +} + // Represents a complete leaderboard record with all scores and associated metadata. message LeaderboardRecord { // The ID of the leaderboard this score belongs to. @@ -764,6 +871,12 @@ message LeaderboardRecordList { string prev_cursor = 4; } +// Leave a group. +message LeaveGroupRequest { + // The group ID to leave. + string group_id = 1; +} + // Link Facebook to the current user's account. message LinkFacebookRequest { // The Facebook account details. @@ -784,6 +897,12 @@ message ListChannelMessagesRequest { string cursor = 4; } +// List all users that are part of a group. +message ListGroupUsersRequest { + // The group ID to list from. + string group_id = 1; +} + // List leaderboard records from a given leaderboard. message ListLeaderboardRecordsRequest { // The ID of the leaderboard to list for. @@ -830,6 +949,12 @@ message ListStorageObjectsRequest { string cursor = 4; // value from StorageObjectList.cursor. } +// List the groups a user is part of, and their relationship to each. +message ListUserGroupsRequest { + // ID of the user. + string user_id = 1; +} + // Represents a realtime match. message Match { // The ID of the match, can be used to join. @@ -874,6 +999,14 @@ message NotificationList { string cacheable_cursor = 2; } +// Promote a set of users in a group to the next role up. +message PromoteGroupUsersRequest { + // The group ID to promote in. + string group_id = 1; + // The users to promote. + repeated string user_ids = 2; +} + // Storage objects to get. message ReadStorageObjectId { // The collection which stores the object. @@ -980,6 +1113,22 @@ message UpdateAccountRequest { google.protobuf.StringValue timezone = 6; } +// Update fields in a given group. +message UpdateGroupRequest { + // The ID of the group to update. + string group_id = 1; + // Name. + google.protobuf.StringValue name = 2; + // Description string. + google.protobuf.StringValue description = 3; + // Lang tag. + google.protobuf.StringValue lang_tag = 4; + // Avatar URL. + google.protobuf.StringValue avatar_url = 5; + // Open is true if anyone should be allowed to join, or false if joins must be approved by a group admin. + google.protobuf.BoolValue open = 6; +} + // A user in the server. message User { // The id of the user's account. @@ -1016,6 +1165,34 @@ message User { google.protobuf.Timestamp update_time = 16; } +// A list of groups belonging to a user, along with the user's role in each group. +message UserGroupList { + // A single group-role pair. + message UserGroup { + // The group role status. + enum State { + // Default case. Assumed as SUPERADMIN state. + STATE_UNSPECIFIED = 0; + // The user is a superadmin with full control of the group. + SUPERADMIN = 1; + // The user is an admin with additional privileges. + ADMIN = 2; + // The user is a regular member. + MEMBER = 3; + // The user has requested to join the group + JOIN_REQUEST = 4; + } + + // Group. + Group group = 1; + // The user's relationship to the group. + int32 state = 2; + } + + // Group-role pairs for a user. + repeated UserGroup user_groups = 1; +} + // A collection of zero or more users. message Users { // The User objects. diff --git a/api/api.swagger.json b/api/api.swagger.json index 13d1976c4..2e48c9083 100644 --- a/api/api.swagger.json +++ b/api/api.swagger.json @@ -645,7 +645,7 @@ ] } }, - "/v2/channel": { + "/v2/channel/{channel_id}": { "get": { "summary": "List a channel's message history.", "operationId": "ListChannelMessages", @@ -660,9 +660,8 @@ "parameters": [ { "name": "channel_id", - "description": "The channel ID to list from.", - "in": "query", - "required": false, + "in": "path", + "required": true, "type": "string" }, { @@ -787,23 +786,112 @@ }, "/v2/group": { "post": { - "summary": "Create one or more new groups with the current user as the owner.", + "summary": "Create a new group with the current user as the owner.", "operationId": "CreateGroup", "responses": { "200": { "description": "", "schema": { - "$ref": "#/definitions/apiGroups" + "$ref": "#/definitions/apiGroup" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiCreateGroupRequest" + } + } + ], + "tags": [ + "Nakama" + ] + } + }, + "/v2/group/{group_id}": { + "delete": { + "summary": "Delete one or more groups by ID.", + "operationId": "DeleteGroup", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Nakama" + ] + }, + "put": { + "summary": "Update fields in a given group.", + "operationId": "UpdateGroup", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiUpdateGroupRequest" + } + } + ], + "tags": [ + "Nakama" + ] + } + }, + "/v2/group/{group_id}/add": { + "post": { + "summary": "Add users to a group.", + "operationId": "AddGroupUsers", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" } } }, "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + }, { "name": "body", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/apiCreateGroupsRequest" + "$ref": "#/definitions/apiAddGroupUsersRequest" } } ], @@ -812,6 +900,163 @@ ] } }, + "/v2/group/{group_id}/join": { + "post": { + "summary": "Immediately join an open group, or request to join a closed one.", + "operationId": "JoinGroup", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiJoinGroupRequest" + } + } + ], + "tags": [ + "Nakama" + ] + } + }, + "/v2/group/{group_id}/kick": { + "post": { + "summary": "Kick a set of users from a group.", + "operationId": "KickGroupUsers", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiKickGroupUsersRequest" + } + } + ], + "tags": [ + "Nakama" + ] + } + }, + "/v2/group/{group_id}/leave": { + "post": { + "summary": "Leave a group the user is a member of.", + "operationId": "LeaveGroup", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiLeaveGroupRequest" + } + } + ], + "tags": [ + "Nakama" + ] + } + }, + "/v2/group/{group_id}/promote": { + "post": { + "summary": "Promote a set of users in a group to the next role up.", + "operationId": "PromoteGroupUsers", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/protobufEmpty" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiPromoteGroupUsersRequest" + } + } + ], + "tags": [ + "Nakama" + ] + } + }, + "/v2/group/{group_id}/user": { + "get": { + "summary": "List all users that are part of a group.", + "operationId": "ListGroupUsers", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/apiGroupUserList" + } + } + }, + "parameters": [ + { + "name": "group_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Nakama" + ] + } + }, "/v2/leaderboard/{leaderboard_id}": { "get": { "summary": "List leaderboard records", @@ -1313,39 +1558,63 @@ "Nakama" ] } + }, + "/v2/user/{user_id}/group": { + "get": { + "summary": "List groups the current user belongs to.", + "operationId": "ListUserGroups", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/apiUserGroupList" + } + } + }, + "parameters": [ + { + "name": "user_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Nakama" + ] + } } }, "definitions": { - "CreateGroupsRequestNewGroup": { + "GroupUserListGroupUser": { "type": "object", "properties": { - "name": { - "type": "string", - "description": "A unique name for the group." - }, - "description": { - "type": "string", - "description": "A description for the group." - }, - "lang_tag": { - "type": "string", - "description": "The language expected to be a tag which follows the BCP-47 spec." - }, - "metadata": { - "type": "string", - "description": "Additional information stored as a JSON object." + "user": { + "$ref": "#/definitions/apiUser", + "description": "User." }, - "avatar_url": { - "type": "string", - "description": "A URL for an avatar image." + "state": { + "type": "integer", + "format": "int32", + "description": "Their relationship to the group." + } + }, + "description": "A single user-role pair." + }, + "UserGroupListUserGroup": { + "type": "object", + "properties": { + "group": { + "$ref": "#/definitions/apiGroup", + "description": "Group." }, - "private": { - "type": "boolean", - "format": "boolean", - "description": "Mark a group as private where only admins can accept members." + "state": { + "type": "integer", + "format": "int32", + "description": "The user's relationship to the group." } }, - "description": "A group to create." + "description": "A single group-role pair." }, "WriteLeaderboardRecordRequestLeaderboardRecordWrite": { "type": "object", @@ -1496,6 +1765,23 @@ }, "description": "Send a Steam token to the server. Used with authenticate/link/unlink." }, + "apiAddGroupUsersRequest": { + "type": "object", + "properties": { + "group_id": { + "type": "string", + "description": "The group to add users to." + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The users to add." + } + }, + "description": "Add users to a group." + }, "apiChannelMessage": { "type": "object", "properties": { @@ -1563,18 +1849,32 @@ }, "description": "A list of channel messages, usually a result of a list operation." }, - "apiCreateGroupsRequest": { + "apiCreateGroupRequest": { "type": "object", "properties": { - "groups": { - "type": "array", - "items": { - "$ref": "#/definitions/CreateGroupsRequestNewGroup" - }, - "description": "The Group objects to create." + "name": { + "type": "string", + "description": "A unique name for the group." + }, + "description": { + "type": "string", + "description": "A description for the group." + }, + "lang_tag": { + "type": "string", + "description": "The language expected to be a tag which follows the BCP-47 spec." + }, + "avatar_url": { + "type": "string", + "description": "A URL for an avatar image." + }, + "open": { + "type": "boolean", + "format": "boolean", + "description": "Mark a group as open or not where only admins can accept members." } }, - "description": "Create one or more groups with the current user as owner." + "description": "Create a group with the current user as owner." }, "apiDeleteStorageObjectId": { "type": "object", @@ -1666,16 +1966,21 @@ "type": "string", "description": "A URL for an avatar image." }, - "private": { + "open": { "type": "boolean", "format": "boolean", - "description": "Mark a group as private where only admins can accept members." + "description": "Anyone can join open groups, otherwise only admins can accept members." }, - "count": { + "edge_count": { "type": "integer", "format": "int32", "description": "The current count of all members in the group." }, + "max_count": { + "type": "integer", + "format": "int32", + "description": "The maximum number of members allowed." + }, "create_time": { "type": "string", "format": "date-time", @@ -1689,18 +1994,45 @@ }, "description": "A group in the server." }, - "apiGroups": { + "apiGroupUserList": { "type": "object", "properties": { - "groups": { + "group_users": { "type": "array", "items": { - "$ref": "#/definitions/apiGroup" + "$ref": "#/definitions/GroupUserListGroupUser" }, - "description": "The Group objects." + "description": "User-role pairs for a group." + } + }, + "description": "A list of users belonging to a group, along with their role." + }, + "apiJoinGroupRequest": { + "type": "object", + "properties": { + "group_id": { + "type": "string", + "description": "The group ID to join." } }, - "description": "A collection of zero or more groups." + "description": "Immediately join an open group, or request to join a closed one." + }, + "apiKickGroupUsersRequest": { + "type": "object", + "properties": { + "group_id": { + "type": "string", + "description": "The group ID to kick from." + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The users to kick." + } + }, + "description": "Kick a set of users from a group." }, "apiLeaderboardRecord": { "type": "object", @@ -1787,6 +2119,16 @@ }, "description": "A set of leaderboard records, may be part of a leaderboard records page or a batch of individual records." }, + "apiLeaveGroupRequest": { + "type": "object", + "properties": { + "group_id": { + "type": "string", + "description": "The group ID to leave." + } + }, + "description": "Leave a group." + }, "apiMatch": { "type": "object", "properties": { @@ -1878,6 +2220,23 @@ }, "description": "A collection of zero or more notifications." }, + "apiPromoteGroupUsersRequest": { + "type": "object", + "properties": { + "group_id": { + "type": "string", + "description": "The group ID to promote in." + }, + "user_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The users to promote." + } + }, + "description": "Promote a set of users in a group to the next role up." + }, "apiReadStorageObjectId": { "type": "object", "properties": { @@ -2087,6 +2446,37 @@ }, "description": "Update a user's account details." }, + "apiUpdateGroupRequest": { + "type": "object", + "properties": { + "group_id": { + "type": "string", + "description": "The ID of the group to update." + }, + "name": { + "type": "string", + "description": "Name." + }, + "description": { + "type": "string", + "description": "Description string." + }, + "lang_tag": { + "type": "string", + "description": "Lang tag." + }, + "avatar_url": { + "type": "string", + "description": "Avatar URL." + }, + "open": { + "type": "boolean", + "format": "boolean", + "description": "Open is true if anyone should be allowed to join, or false if joins must be approved by a group admin." + } + }, + "description": "Update fields in a given group." + }, "apiUser": { "type": "object", "properties": { @@ -2161,6 +2551,19 @@ }, "description": "A user in the server." }, + "apiUserGroupList": { + "type": "object", + "properties": { + "user_groups": { + "type": "array", + "items": { + "$ref": "#/definitions/UserGroupListUserGroup" + }, + "description": "Group-role pairs for a user." + } + }, + "description": "A list of groups belonging to a user, along with the user's role in each group." + }, "apiUsers": { "type": "object", "properties": { diff --git a/console/console.pb.go b/console/console.pb.go index 89c06678f..caee27e1d 100644 --- a/console/console.pb.go +++ b/console/console.pb.go @@ -52,12 +52,14 @@ type AccountExport struct { Friends []*nakama_api.Friend `protobuf:"bytes,3,rep,name=friends" json:"friends,omitempty"` // The user's groups. Groups []*nakama_api.Group `protobuf:"bytes,4,rep,name=groups" json:"groups,omitempty"` + // The user's chat messages. + Messages []*nakama_api.ChannelMessage `protobuf:"bytes,5,rep,name=messages" json:"messages,omitempty"` // The user's leaderboard records. - LeaderboardRecords []*nakama_api.LeaderboardRecord `protobuf:"bytes,5,rep,name=leaderboard_records,json=leaderboardRecords" json:"leaderboard_records,omitempty"` + LeaderboardRecords []*nakama_api.LeaderboardRecord `protobuf:"bytes,6,rep,name=leaderboard_records,json=leaderboardRecords" json:"leaderboard_records,omitempty"` // The user's notifications. - Notifications []*nakama_api.Notification `protobuf:"bytes,6,rep,name=notifications" json:"notifications,omitempty"` + Notifications []*nakama_api.Notification `protobuf:"bytes,7,rep,name=notifications" json:"notifications,omitempty"` // The user's wallet ledger items. - WalletLedgers []*WalletLedger `protobuf:"bytes,7,rep,name=wallet_ledgers,json=walletLedgers" json:"wallet_ledgers,omitempty"` + WalletLedgers []*WalletLedger `protobuf:"bytes,8,rep,name=wallet_ledgers,json=walletLedgers" json:"wallet_ledgers,omitempty"` } func (m *AccountExport) Reset() { *m = AccountExport{} } @@ -93,6 +95,13 @@ func (m *AccountExport) GetGroups() []*nakama_api.Group { return nil } +func (m *AccountExport) GetMessages() []*nakama_api.ChannelMessage { + if m != nil { + return m.Messages + } + return nil +} + func (m *AccountExport) GetLeaderboardRecords() []*nakama_api.LeaderboardRecord { if m != nil { return m.LeaderboardRecords @@ -399,55 +408,56 @@ var _Console_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("console/console.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 788 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x94, 0xdd, 0x6e, 0xdc, 0x44, - 0x14, 0xc7, 0xe5, 0xdd, 0x66, 0xdd, 0x4c, 0xd8, 0xa8, 0x4c, 0x5a, 0x6a, 0xdc, 0x44, 0x71, 0x1d, - 0x04, 0x65, 0xd5, 0xd8, 0xc8, 0xbd, 0x0b, 0x12, 0x62, 0x1b, 0x0a, 0x8a, 0x14, 0x02, 0x72, 0x22, - 0x21, 0x71, 0xb3, 0x9a, 0xb5, 0x4f, 0xbc, 0x4e, 0xec, 0x19, 0x33, 0x33, 0xce, 0x12, 0xa1, 0xdc, - 0xf0, 0x08, 0xf0, 0x04, 0x3c, 0x13, 0xd7, 0xdc, 0x71, 0x05, 0x0f, 0xc0, 0x2d, 0xf2, 0xcc, 0x38, - 0xf1, 0xee, 0x2a, 0xd0, 0x0b, 0xcb, 0x3a, 0xe7, 0xff, 0x3b, 0x1f, 0xf6, 0x9c, 0x33, 0xe8, 0x49, - 0xc2, 0xa8, 0x60, 0x05, 0x84, 0xe6, 0x1d, 0x54, 0x9c, 0x49, 0x86, 0x37, 0x29, 0xb9, 0x24, 0x25, - 0x09, 0x8c, 0xd7, 0x1d, 0x65, 0xb9, 0x9c, 0xd5, 0xd3, 0x20, 0x61, 0x65, 0x38, 0x03, 0xce, 0xf2, - 0xa4, 0x20, 0x53, 0x11, 0x6a, 0x2a, 0x24, 0x55, 0xde, 0x3c, 0x3a, 0xd6, 0xdd, 0xce, 0x18, 0xcb, - 0x0a, 0xd0, 0x5e, 0x4a, 0x99, 0x24, 0x32, 0x67, 0x54, 0x18, 0xf5, 0x99, 0x51, 0x95, 0x35, 0xad, - 0xcf, 0x43, 0x28, 0x2b, 0x79, 0x6d, 0xc4, 0xdd, 0x65, 0x51, 0xe6, 0x25, 0x08, 0x49, 0xca, 0xca, - 0x00, 0x2f, 0xd5, 0x2b, 0xd9, 0xcf, 0x80, 0xee, 0x8b, 0x39, 0xc9, 0x32, 0xe0, 0x21, 0xab, 0x54, - 0xfe, 0xd5, 0x5a, 0xfe, 0x6f, 0x7d, 0x34, 0x1c, 0x27, 0x09, 0xab, 0xa9, 0x7c, 0xf3, 0x63, 0xc5, - 0xb8, 0xc4, 0xfb, 0xc8, 0x26, 0xda, 0xe1, 0x58, 0x9e, 0xf5, 0x62, 0x23, 0xda, 0x0a, 0xcc, 0x97, - 0x36, 0xfd, 0x1b, 0x36, 0x6e, 0x19, 0xfc, 0x0a, 0xd9, 0x6c, 0x7a, 0x01, 0x89, 0x14, 0x4e, 0xcf, - 0xeb, 0xbf, 0xd8, 0x88, 0xde, 0xef, 0xe2, 0xa7, 0x92, 0x71, 0x92, 0xc1, 0x37, 0x8a, 0x88, 0x5b, - 0x12, 0xbf, 0x44, 0xf6, 0x39, 0xcf, 0x81, 0xa6, 0xc2, 0xe9, 0xab, 0x20, 0xdc, 0x0d, 0xfa, 0x52, - 0x49, 0x71, 0x8b, 0xe0, 0x8f, 0xd1, 0x20, 0xe3, 0xac, 0xae, 0x84, 0xf3, 0x40, 0xc1, 0xef, 0x76, - 0xe1, 0xaf, 0x1a, 0x25, 0x36, 0x00, 0x3e, 0x41, 0x5b, 0x05, 0x90, 0x14, 0xf8, 0x94, 0x11, 0x9e, - 0x4e, 0x38, 0x24, 0x8c, 0xa7, 0xc2, 0x59, 0x53, 0x71, 0x3b, 0xdd, 0xb8, 0xe3, 0x3b, 0x2c, 0x56, - 0x54, 0x8c, 0x8b, 0x65, 0x97, 0xc0, 0x9f, 0xa1, 0x21, 0x65, 0x32, 0x3f, 0xcf, 0x13, 0xfd, 0xd7, - 0x9c, 0x81, 0xca, 0xe4, 0x74, 0x33, 0x9d, 0x74, 0x80, 0x78, 0x11, 0xc7, 0x87, 0x68, 0x73, 0x4e, - 0x8a, 0x02, 0xe4, 0xa4, 0x80, 0x34, 0x03, 0x2e, 0x1c, 0x5b, 0x25, 0xd8, 0x0e, 0x16, 0xa7, 0x27, - 0xf8, 0x4e, 0x51, 0xc7, 0x0a, 0x8a, 0x87, 0xf3, 0x8e, 0x25, 0x7c, 0x1f, 0x3d, 0x32, 0xbf, 0xfd, - 0x28, 0x8d, 0xe1, 0x87, 0x1a, 0x84, 0xc4, 0x9b, 0xa8, 0x97, 0xa7, 0xea, 0x80, 0xd6, 0xe3, 0x5e, - 0x9e, 0xfa, 0x5f, 0xa3, 0xad, 0x71, 0x2d, 0x67, 0x40, 0x65, 0x53, 0x1b, 0x5a, 0xcc, 0x45, 0x0f, - 0x6b, 0x01, 0x9c, 0x92, 0x12, 0x0c, 0x7c, 0x6b, 0x37, 0x5a, 0x45, 0x84, 0x98, 0x33, 0x9e, 0x3a, - 0x3d, 0xad, 0xb5, 0xb6, 0xbf, 0x8b, 0xec, 0x53, 0x10, 0x22, 0x67, 0x14, 0x3f, 0x46, 0x6b, 0x92, - 0x5d, 0x02, 0x35, 0xf1, 0xda, 0xf0, 0xff, 0xb6, 0xd0, 0x3b, 0xdd, 0x9e, 0x97, 0x1b, 0xc2, 0x4f, - 0x91, 0xdd, 0x54, 0x9a, 0xe4, 0x6d, 0xf2, 0x41, 0x63, 0x1e, 0xa5, 0x78, 0x1b, 0xad, 0x27, 0x33, - 0x42, 0x33, 0x10, 0x20, 0x9d, 0xbe, 0x92, 0xee, 0x1c, 0x4d, 0x53, 0x25, 0x48, 0x92, 0x12, 0x49, - 0x9c, 0x07, 0xba, 0xa9, 0xd6, 0xc6, 0x9f, 0xa2, 0x8d, 0x84, 0x03, 0x91, 0x30, 0x69, 0x66, 0xde, - 0x79, 0xa8, 0xa6, 0xd3, 0x0d, 0xf4, 0x42, 0x04, 0xed, 0x42, 0x04, 0x67, 0xed, 0x42, 0xc4, 0x48, - 0xe3, 0x8d, 0xa3, 0x09, 0xae, 0xab, 0xf4, 0x36, 0x78, 0xfd, 0xff, 0x83, 0x35, 0xde, 0x38, 0xa2, - 0x3f, 0x7a, 0xc8, 0x3e, 0xd4, 0x27, 0x85, 0x2f, 0xd1, 0xf0, 0x0b, 0x28, 0x40, 0x82, 0x39, 0x13, - 0xec, 0x2d, 0x9f, 0xe5, 0xf2, 0x61, 0xb9, 0xef, 0xad, 0x94, 0x79, 0xd3, 0x6c, 0xb4, 0xef, 0xfd, - 0xfc, 0xfb, 0x9f, 0xbf, 0xf6, 0xdc, 0x91, 0x13, 0x5e, 0x45, 0xed, 0xed, 0x12, 0x9a, 0xc5, 0x0a, - 0x7f, 0xca, 0xd3, 0x1b, 0x7c, 0x8d, 0x86, 0x7a, 0x2d, 0xdf, 0xbe, 0xd8, 0xce, 0x3d, 0x84, 0xce, - 0xe3, 0x7f, 0xa4, 0x6a, 0x3e, 0xc7, 0xbb, 0xf7, 0xd5, 0x0c, 0x41, 0xdf, 0x03, 0x17, 0x68, 0xed, - 0x98, 0x65, 0x39, 0xc5, 0x7b, 0x2b, 0x09, 0x57, 0x07, 0xcd, 0x7d, 0xba, 0x0c, 0x99, 0xf1, 0xf1, - 0xf7, 0x54, 0xbd, 0x1d, 0x7f, 0xf1, 0x1b, 0x3b, 0x19, 0x0e, 0xac, 0xd1, 0xeb, 0x7f, 0xac, 0x5f, - 0xc6, 0x7f, 0x59, 0xf8, 0x06, 0x3d, 0x39, 0x51, 0x59, 0x3c, 0x03, 0x7a, 0xe3, 0x6f, 0x8f, 0xbc, - 0xab, 0xc8, 0x9f, 0xa0, 0xe7, 0x67, 0x33, 0xf0, 0x8c, 0xd8, 0xd4, 0x67, 0x5c, 0x78, 0x1f, 0x7a, - 0x87, 0x8c, 0x4a, 0x9e, 0x4f, 0x6b, 0xc9, 0xb8, 0xc0, 0x1f, 0xcc, 0xa4, 0xac, 0xc4, 0x41, 0x18, - 0xfe, 0xd7, 0x45, 0xec, 0x3e, 0x9e, 0x41, 0x51, 0xb0, 0xcf, 0xef, 0x84, 0x86, 0x8b, 0xfa, 0x51, - 0xf0, 0xc9, 0xc8, 0xb2, 0xa2, 0x47, 0xa4, 0xaa, 0x0a, 0xb3, 0xbd, 0xe1, 0x85, 0x60, 0xf4, 0x60, - 0xc5, 0xc3, 0x5f, 0xa3, 0x3d, 0xd3, 0x88, 0x00, 0x7e, 0x05, 0xfc, 0xb6, 0xd9, 0x94, 0x25, 0x75, - 0x09, 0x54, 0x5f, 0xb0, 0xf8, 0x59, 0xdb, 0xce, 0x62, 0xa9, 0x30, 0x65, 0x89, 0xf8, 0xde, 0x36, - 0x31, 0xd3, 0x81, 0x1a, 0x89, 0x57, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x20, 0xe1, 0x51, - 0x65, 0x06, 0x00, 0x00, + // 813 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x94, 0x41, 0x6f, 0xdc, 0x44, + 0x14, 0xc7, 0xe5, 0xdd, 0x26, 0x4e, 0x26, 0x6c, 0x54, 0x26, 0x2d, 0x35, 0x6e, 0xa2, 0xb8, 0x0e, + 0x82, 0x12, 0x35, 0x36, 0x72, 0x25, 0x0e, 0x41, 0x42, 0xa4, 0x4b, 0x41, 0x91, 0xd2, 0x80, 0xdc, + 0x4a, 0x48, 0x5c, 0x56, 0xb3, 0xf6, 0x8b, 0xed, 0xc4, 0x9e, 0x31, 0x33, 0xe3, 0x2c, 0x15, 0xea, + 0x85, 0x8f, 0x00, 0x1f, 0x8d, 0x13, 0x07, 0x6e, 0x9c, 0xe0, 0x03, 0x70, 0x45, 0x9e, 0x19, 0x6f, + 0xbc, 0xbb, 0x0a, 0xf4, 0xb0, 0xb2, 0xde, 0x7b, 0xbf, 0xf7, 0x7f, 0x6f, 0xf6, 0xcd, 0x1b, 0x74, + 0x3f, 0x61, 0x54, 0xb0, 0x12, 0x42, 0xf3, 0x0d, 0x6a, 0xce, 0x24, 0xc3, 0xdb, 0x94, 0x5c, 0x91, + 0x8a, 0x04, 0xc6, 0xeb, 0x1e, 0x66, 0x85, 0xcc, 0x9b, 0x69, 0x90, 0xb0, 0x2a, 0xcc, 0x81, 0xb3, + 0x22, 0x29, 0xc9, 0x54, 0x84, 0x9a, 0x0a, 0x49, 0x5d, 0xb4, 0x3f, 0x9d, 0xeb, 0xee, 0x66, 0x8c, + 0x65, 0x25, 0x68, 0x2f, 0xa5, 0x4c, 0x12, 0x59, 0x30, 0x2a, 0x4c, 0xf4, 0xa1, 0x89, 0x2a, 0x6b, + 0xda, 0x5c, 0x84, 0x50, 0xd5, 0xf2, 0xb5, 0x09, 0xee, 0x2f, 0x07, 0x65, 0x51, 0x81, 0x90, 0xa4, + 0xaa, 0x0d, 0xf0, 0x44, 0x7d, 0x92, 0xa3, 0x0c, 0xe8, 0x91, 0x98, 0x91, 0x2c, 0x03, 0x1e, 0xb2, + 0x5a, 0xe9, 0xaf, 0xd6, 0xf2, 0x7f, 0x1f, 0xa2, 0xd1, 0x49, 0x92, 0xb0, 0x86, 0xca, 0xe7, 0x3f, + 0xd6, 0x8c, 0x4b, 0x7c, 0x84, 0x6c, 0xa2, 0x1d, 0x8e, 0xe5, 0x59, 0x8f, 0xb7, 0xa2, 0x9d, 0xc0, + 0x9c, 0xb4, 0xed, 0xdf, 0xb0, 0x71, 0xc7, 0xe0, 0xa7, 0xc8, 0x66, 0xd3, 0x4b, 0x48, 0xa4, 0x70, + 0x06, 0xde, 0xf0, 0xf1, 0x56, 0xf4, 0x7e, 0x1f, 0x7f, 0x29, 0x19, 0x27, 0x19, 0x7c, 0xa3, 0x88, + 0xb8, 0x23, 0xf1, 0x13, 0x64, 0x5f, 0xf0, 0x02, 0x68, 0x2a, 0x9c, 0xa1, 0x4a, 0xc2, 0xfd, 0xa4, + 0xaf, 0x54, 0x28, 0xee, 0x10, 0xfc, 0x31, 0x5a, 0xcf, 0x38, 0x6b, 0x6a, 0xe1, 0xdc, 0x51, 0xf0, + 0xbb, 0x7d, 0xf8, 0xeb, 0x36, 0x12, 0x1b, 0x00, 0x7f, 0x8a, 0x36, 0x2a, 0x10, 0x82, 0x64, 0x20, + 0x9c, 0x35, 0x05, 0xbb, 0x7d, 0x78, 0x9c, 0x13, 0x4a, 0xa1, 0x7c, 0xa1, 0x91, 0x78, 0xce, 0xe2, + 0x73, 0xb4, 0x53, 0x02, 0x49, 0x81, 0x4f, 0x19, 0xe1, 0xe9, 0x84, 0x43, 0xc2, 0x78, 0x2a, 0x9c, + 0x75, 0x25, 0xb1, 0xd7, 0x97, 0x38, 0xbb, 0xc1, 0x62, 0x45, 0xc5, 0xb8, 0x5c, 0x76, 0x09, 0xfc, + 0x39, 0x1a, 0x51, 0x26, 0x8b, 0x8b, 0x22, 0xd1, 0xff, 0xb6, 0x63, 0x2b, 0x25, 0xa7, 0xaf, 0x74, + 0xde, 0x03, 0xe2, 0x45, 0x1c, 0x8f, 0xd1, 0xf6, 0x8c, 0x94, 0x25, 0xc8, 0x49, 0x09, 0x69, 0x06, + 0x5c, 0x38, 0x1b, 0x4a, 0x60, 0x37, 0x58, 0xbc, 0x75, 0xc1, 0x77, 0x8a, 0x3a, 0x53, 0x50, 0x3c, + 0x9a, 0xf5, 0x2c, 0xe1, 0xfb, 0xe8, 0xae, 0x19, 0xd7, 0x69, 0x1a, 0xc3, 0x0f, 0x0d, 0x08, 0x89, + 0xb7, 0xd1, 0xa0, 0x48, 0xd5, 0x60, 0x37, 0xe3, 0x41, 0x91, 0xfa, 0x2f, 0xd0, 0xce, 0x49, 0x23, + 0x73, 0xa0, 0xb2, 0xad, 0x0d, 0x1d, 0xe6, 0xa2, 0x8d, 0x46, 0x00, 0xa7, 0xa4, 0x02, 0x03, 0xcf, + 0xed, 0x36, 0x56, 0x13, 0x21, 0x66, 0x8c, 0xa7, 0xce, 0x40, 0xc7, 0x3a, 0xdb, 0xdf, 0x47, 0xf6, + 0x4b, 0x10, 0xa2, 0x60, 0x14, 0xdf, 0x43, 0x6b, 0x92, 0x5d, 0x01, 0x35, 0xf9, 0xda, 0xf0, 0xff, + 0xb6, 0xd0, 0x3b, 0xfd, 0x9e, 0x97, 0x1b, 0xc2, 0x0f, 0x90, 0xdd, 0x56, 0x9a, 0x14, 0x9d, 0xf8, + 0x7a, 0x6b, 0x9e, 0xa6, 0x78, 0x17, 0x6d, 0x26, 0x39, 0xa1, 0x19, 0x08, 0x90, 0xce, 0x50, 0x85, + 0x6e, 0x1c, 0x6d, 0x53, 0x15, 0x48, 0x92, 0x12, 0x49, 0x9c, 0x3b, 0xba, 0xa9, 0xce, 0xc6, 0x9f, + 0xa1, 0xad, 0x84, 0x03, 0x91, 0x30, 0x69, 0x77, 0xc5, 0xd9, 0x50, 0xb7, 0xda, 0x0d, 0xf4, 0x22, + 0x05, 0xdd, 0x22, 0x05, 0xaf, 0xba, 0x45, 0x8a, 0x91, 0xc6, 0x5b, 0x47, 0x9b, 0xdc, 0xd4, 0xe9, + 0x3c, 0x79, 0xf3, 0xff, 0x93, 0x35, 0xde, 0x3a, 0xa2, 0x3f, 0x06, 0xc8, 0x1e, 0xeb, 0x49, 0xe1, + 0x2b, 0x34, 0xfa, 0x12, 0x4a, 0x90, 0x60, 0x66, 0x82, 0xbd, 0xe5, 0x59, 0x2e, 0x0f, 0xcb, 0x7d, + 0x6f, 0xa5, 0xcc, 0xf3, 0xf6, 0x25, 0xf0, 0xbd, 0x9f, 0x7f, 0xfb, 0xf3, 0xd7, 0x81, 0x7b, 0xe8, + 0x84, 0xd7, 0x51, 0xf7, 0x2a, 0x85, 0x66, 0x21, 0xc3, 0x9f, 0x8a, 0xf4, 0x0d, 0x7e, 0x8d, 0x46, + 0x7a, 0x9d, 0xdf, 0xbe, 0xd8, 0xde, 0x2d, 0x84, 0xd6, 0xf1, 0x3f, 0x52, 0x35, 0x1f, 0xe1, 0xfd, + 0xdb, 0x6a, 0x86, 0xa0, 0xdf, 0x8f, 0x4b, 0xb4, 0x76, 0xc6, 0xb2, 0x82, 0xe2, 0x83, 0x15, 0xc1, + 0xd5, 0x8b, 0xe6, 0x3e, 0x58, 0x86, 0xcc, 0xf5, 0xf1, 0x0f, 0x54, 0xbd, 0x3d, 0x7f, 0xf1, 0x8c, + 0x3d, 0x85, 0x63, 0xeb, 0xf0, 0xd9, 0x3f, 0xd6, 0x2f, 0x27, 0x7f, 0x59, 0xf8, 0x0d, 0xba, 0x7f, + 0xae, 0x54, 0x3c, 0x03, 0x7a, 0x27, 0xdf, 0x9e, 0x7a, 0xd7, 0x91, 0x3f, 0x41, 0x8f, 0x5e, 0xe5, + 0xe0, 0x99, 0x60, 0x5b, 0x9f, 0x71, 0xe1, 0x7d, 0xe8, 0x8d, 0x19, 0x95, 0xbc, 0x98, 0x36, 0x92, + 0x71, 0x81, 0x3f, 0xc8, 0xa5, 0xac, 0xc5, 0x71, 0x18, 0xfe, 0xd7, 0x03, 0xee, 0xde, 0xcb, 0xa1, + 0x2c, 0xd9, 0x17, 0x37, 0x81, 0x96, 0x8b, 0x86, 0x51, 0xf0, 0xc9, 0xa1, 0x65, 0x45, 0x77, 0x49, + 0x5d, 0x97, 0x66, 0x7b, 0xc3, 0x4b, 0xc1, 0xe8, 0xf1, 0x8a, 0x87, 0x3f, 0x43, 0x07, 0xa6, 0x11, + 0x01, 0xfc, 0x1a, 0xf8, 0xbc, 0xd9, 0x94, 0x25, 0x4d, 0x05, 0x54, 0x3f, 0xcc, 0xf8, 0x61, 0xd7, + 0xce, 0x62, 0xa9, 0x30, 0x65, 0x89, 0xf8, 0xde, 0x36, 0x39, 0xd3, 0x75, 0x75, 0x25, 0x9e, 0xfe, + 0x1b, 0x00, 0x00, 0xff, 0xff, 0x8b, 0xa2, 0xed, 0x2b, 0x9d, 0x06, 0x00, 0x00, } diff --git a/console/console.proto b/console/console.proto index 297a3b1a4..a01647cc0 100644 --- a/console/console.proto +++ b/console/console.proto @@ -81,12 +81,14 @@ message AccountExport { repeated nakama.api.Friend friends = 3; // The user's groups. repeated nakama.api.Group groups = 4; + // The user's chat messages. + repeated nakama.api.ChannelMessage messages = 5; // The user's leaderboard records. - repeated nakama.api.LeaderboardRecord leaderboard_records = 5; + repeated nakama.api.LeaderboardRecord leaderboard_records = 6; // The user's notifications. - repeated nakama.api.Notification notifications = 6; + repeated nakama.api.Notification notifications = 7; // The user's wallet ledger items. - repeated WalletLedger wallet_ledgers = 7; + repeated WalletLedger wallet_ledgers = 8; } /** diff --git a/console/console.swagger.json b/console/console.swagger.json index 7014cb93e..84e5faf3a 100644 --- a/console/console.swagger.json +++ b/console/console.swagger.json @@ -142,6 +142,52 @@ }, "description": "Send a device to the server. Used with authenticate/link/unlink and user." }, + "apiChannelMessage": { + "type": "object", + "properties": { + "channel_id": { + "type": "string", + "description": "The channel this message belongs to." + }, + "message_id": { + "type": "string", + "description": "The unique ID of this message." + }, + "code": { + "type": "integer", + "format": "int32", + "description": "The code representing a message type or category." + }, + "sender_id": { + "type": "string", + "description": "Message sender, usually a user ID." + }, + "username": { + "type": "string", + "description": "The username of the message sender, if any." + }, + "content": { + "type": "string", + "description": "The content payload." + }, + "create_time": { + "type": "string", + "format": "date-time", + "description": "The UNIX time when the message was created." + }, + "update_time": { + "type": "string", + "format": "date-time", + "description": "The UNIX time when the message was last updated." + }, + "persistent": { + "type": "boolean", + "format": "boolean", + "description": "True if the message was persisted to the channel's history, false otherwise." + } + }, + "description": "A message sent on a channel." + }, "apiFriend": { "type": "object", "properties": { @@ -188,16 +234,21 @@ "type": "string", "description": "A URL for an avatar image." }, - "private": { + "open": { "type": "boolean", "format": "boolean", - "description": "Mark a group as private where only admins can accept members." + "description": "Anyone can join open groups, otherwise only admins can accept members." }, - "count": { + "edge_count": { "type": "integer", "format": "int32", "description": "The current count of all members in the group." }, + "max_count": { + "type": "integer", + "format": "int32", + "description": "The maximum number of members allowed." + }, "create_time": { "type": "string", "format": "date-time", @@ -453,6 +504,13 @@ }, "description": "The user's groups." }, + "messages": { + "type": "array", + "items": { + "$ref": "#/definitions/apiChannelMessage" + }, + "description": "The user's chat messages." + }, "leaderboard_records": { "type": "array", "items": { @@ -533,11 +591,33 @@ }, "description": "*\nA user's session used to authenticate messages." }, + "protobufBoolValue": { + "type": "object", + "properties": { + "value": { + "type": "boolean", + "format": "boolean", + "description": "The bool value." + } + }, + "description": "Wrapper message for `bool`.\n\nThe JSON representation for `BoolValue` is JSON `true` and `false`." + }, "protobufEmpty": { "type": "object", "description": "service Foo {\n rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);\n }\n\nThe JSON representation for `Empty` is empty JSON object `{}`.", "title": "A generic empty message that you can re-use to avoid defining duplicated\nempty messages in your APIs. A typical example is to use it as the request\nor the response type of an API method. For instance:" }, + "protobufInt32Value": { + "type": "object", + "properties": { + "value": { + "type": "integer", + "format": "int32", + "description": "The int32 value." + } + }, + "description": "Wrapper message for `int32`.\n\nThe JSON representation for `Int32Value` is JSON number." + }, "protobufStringValue": { "type": "object", "properties": { diff --git a/main.go b/main.go index 7e2d2acff..03baeb240 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( _ "github.com/lib/pq" "github.com/satori/go.uuid" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) const cookieFilename = ".cookie" @@ -66,7 +67,7 @@ func main() { // Initialize the global random obj with customs seed. rand.Seed(time.Now().UnixNano()) - tmpLogger := server.NewJSONLogger(os.Stdout, "info") + tmpLogger := server.NewJSONLogger(os.Stdout, zapcore.InfoLevel) if len(os.Args) > 1 { switch os.Args[1] { @@ -79,7 +80,7 @@ func main() { } config := server.ParseArgs(tmpLogger, os.Args) - logger, startupLogger := server.SetupLogging(config) + logger, startupLogger := server.SetupLogging(tmpLogger, config) startupLogger.Info("Nakama starting") startupLogger.Info("Node", zap.String("name", config.GetName()), zap.String("version", semver), zap.String("runtime", runtime.Version()), zap.Int("cpu", runtime.NumCPU())) @@ -170,6 +171,7 @@ func dbConnect(multiLogger *zap.Logger, config server.Config) (*sql.DB, string) parsedUrl.Path = "/nakama" } + multiLogger.Debug("Complete database connection URL", zap.String("raw_url", parsedUrl.String())) db, err := sql.Open("postgres", parsedUrl.String()) if err != nil { multiLogger.Fatal("Error connecting to database", zap.Error(err)) diff --git a/migrate/migrate-packr.go b/migrate/migrate-packr.go index 5934a9c91..a81c98e10 100644 --- a/migrate/migrate-packr.go +++ b/migrate/migrate-packr.go @@ -9,5 +9,5 @@ import "github.com/gobuffalo/packr" // Go binary. You can use the "packr clean" command to clean up this, // and any other packr generated files. func init() { - packr.PackJSONBytes("./sql", "20180103142001_initial_schema.sql", "\"H4sIAAAAAAAA/7xZW3PbuhF+16/Y8UMtpbQsO8npmaTtDC3RCRtFSkXqnKQvHAhcizgmARYApaid/vcOeJFIibo4vTAziUgsFovdb79dILevOvAKhiLdSLaMNNwP7n4GP0KYkGeSELAzHQmpOpDLjRlFrjCEjIcoQUcIdkpohNWIBb+gVExwuO8PoGsErsqhq957o2IjMkjIBrjQkCkEHTEFTyxGwO8UUw2MAxVJGjPCKcKa6Shfp9TSNzq+lTrEQhPGgQAV6QbEU10QiC6NjrRO393ertfrPsmN7Qu5vI0LMXU7dofOxHNu7vuDcsKcx6gUSPx7xiSGsNgASdOYUbKIEWKyBiGBLCViCFoYg9eSacaXFijxpNdEolETMqUlW2S64a/KPKYaAoID4XBle+B6V/Bge65nGSW/uv7H6dyHX+3ZzJ74ruPBdAbD6WTk+u504sH0EezJN/jkTkYWINMRSsDvqTQ7EBKY8SSGuds8xIYJT6IwSaVI2ROjEBO+zMgSYSlWKDnjS0hRJkyZiCogPDRqYpYwTXT+6WBfZqHbTufmBn6fsKUkGmGedoYzx/Yd8O2HsQPuI0ymPjhfXc/3DAakgm4HAODLzP1sz77BJ+cbdFnYszr5ZxZC7ZnP3dHuzWiazMdjK5c0yjhJsBj7xZ4NP9qz7t39zz0wPvP8me1O/GLNoBIOnnED84n717mzpy5kKo3JJihUVuru377tFeNkRTSRQSbj+nK78ZubHHzq3e2tFiJWfYb6KUdfpJP4dkHTN3/IBY3jA02We3Ybs2HkPNrzsQ/XyK8LtbGgufub0vmyZknsL/tw5REOj5JwyhQVFgztq3yuZgn+Q3A8OfcLKfDgswShO/fgdzAknISkVyhJUJOQaFIo+Ys3nTxsA7I195//ut5z55rEMepK8OJpmBAWbwXrJkMZtkIuJUqthSzB8vDNd+ztrOFHZ/gJujHypY66lWQP/giv7weDQRmvJ0JxIcRzkCOuCZ/6SkshljEGJS5PyJEEKXKN0sgel1MaSVKpOyFHM6VFcn5dDJcYUJHx3NkG8XDg6EHlk5rwn/8Eg96e96lEojEwuAEA3/3seL79+Yv/t5ouLtbd/XlZGv7QvBVK9rQ5PW9oe353ALZXH99XFDJl+LrU9EOKOr33OZV5qLMUvI3SmOTk0e+4E8+Z+ca304rCWGhtCajXKSI0njsedK8H5XPT8lf1XFtwfV3Mm04MWz2O3aFvaBBGU2PSR3fy4X3nHJcGIa4YxaOMar4+TmeO+2FSfM0nmVVmzqMzcyZDx9vtqGdsGTljx3eMs4b2yGkh5SYS20i5wmyTvKHp6XM7M0ht25cSmaQmHS1Qmmi0IBWKGYZs2+9WuleYcPG2DzSFqDTjORX/gAO3drSUtaYDq92Ugw/uh11KbyUNe89Na2aquhJSg5CmNgsOUqxVvyUpm0lxKimbOz1lax6AXXy9z/Z4XFm7o56G1U+SIQ+7g54FjK+Yxu7d9mfYve9ZsIgFfcaw+7pnQYgxmu9vehYQSSO2wrD7tlf4tKzidUTsBekc0LjQpvQV9bVbVfFH9+tn5x1QQZ+lIDS6Ns0QiTcKpWnkTC9LY1yZ1ouLbBnBOkLeoM6IKBg53hASEaIJiumZGA/xe/8Q0GXGWHUNFvzP0nc/KWudUt0dAQuPt0qXJHmJkGzxG1LdII6imO9LUsE1FjVsr1s42S9Q4+E2/O2nywSXRLMVworEGSogEkEVJC9RoVzlfbMxGU2bW2zv9ObqIW+WnGMJdg6QSgtJ2nmPijhGakJjgUQSWvCMG6sKxX8RLLuFznG9wUf1/OdVoewITHTKkRfAYFUeROumvL7vHcIgCd+a9IzMAbJYS+QILWJvHHsMTdXqd1UvlQu3dVHmgIiXaimEzzVjl8HrsBm7dF6DUOtQa4DMwLdErzsZOV/30LubFxjfBOU0wyMBC78bxG0BfgjnLQ8+4+ZMmiSoVJUmzeZAS9NcG9o17UH+UlLQ9j1ERSVLtZDbTzFZYNxOwM1uoqKGF+TUngqJTyiRlz1JTct2T8dzs3k0PsilFqq+uQEaEZ2XW/MjKLCR19ylFFka/CYYz8tu8UrCovAWbzGSFebFt3h/ZvS5+3b7mkqRCFOefzKdbJ2JW7Df0g90mmTbsqW6aPO4f8A5Da07GLSZ0iJaq1NHDTjAz1nRHFdnbK3XvRbaO8p5dSDtPJdrbBzi4PLWb69hvHRip9aKVeHM8+dctYuRhCgXgsjwMJWrG6E90B/1I8nvLvO7qhUCPEynY8eeNJ34aI+9PCVNyxwULfNRoN41CwdRNE8jE/7unQG8SFESA4PL0G6ULFAVyahQl40vlZgg13kOhli9ve7lIVaoA0UjDLMYt1v/6U3t+oZKwc0pICH6HVy9gtqfq87+5c2F0NoD0AUgeEGkA4lUtAW8LmMAhN9TJjclGysqpPknW5S/xJrvGp8Gwzb1NDi2AbjjPNvUcBxylQ0vZK66vhyMZkc7hDdOfAcXOIXwYbtQeeYyJZXwoR6eJUFNUeM66aB32QkfKvo/Ia+Fty6cVcPX0Vln755q3FehwYITSD7LicW1aRBjuER5mCQnD4sv6P7bqLWO35Zeot7D70k3QhgRvjTEdRj3C+FxISB+CA8/Rl/51rVIFkoL3tJ2NoKxO5GddlqLi1+82+rOcvvfLyOx5p3RbPplt4/WPbxvF2qg74jMIZefFzwiUfa8R0bLs8KR0fplxRGR7T3iqfHiBvWEhHrf+XcAAAD//x28Hbw4HQAA\"") -} + packr.PackJSONBytes("./sql", "20180103142001_initial_schema.sql", "\"H4sIAAAAAAAA/7xZb3PbNtJ/r0+x4xePpT60LTtJ20nam6ElOuFVoXKi1Cb3hgORawk1CbAAJFl3c9/9BvwjESIl0b7euTONSC4Wi93f/rBY3HzXge9gwNOtoIulgrv+7Y8wXSJ45IkkBOyVWnIhO5DJjWiITGIEKxahALVEsFMSLrH8YsGvKCTlDO6u+9DVAhfFp4veB61iy1eQkC0wrmAlEdSSSnikMQI+h5gqoAxCnqQxJSxE2FC1zOYptFxrHd8KHXyuCGVAIOTpFvhjVRCIKoxeKpW+v7nZbDbXJDP2movFTZyLyZuRO3A837m6u+4XA2YsRilB4B8rKjCC+RZImsY0JPMYISYb4ALIQiBGoLg2eCOoomxhgeSPakMEajURlUrQ+UoZ/irNo9IQ4AwIgwvbB9e/gHvbd31LK/nNnX4az6bwmz2Z2N7UdXwYT2Aw9obu1B17PowfwPa+wS+uN7QAqVqiAHxOhV4BF0C1JzHK3OYjGiY88twkmWJIH2kIMWGLFVkgLPgaBaNsASmKhEodUQmERVpNTBOqiMpe1dalJ7rpdK6u4P8TuhBEIczSzmDi2FMHpvb9yAH3AbzxFJyvrj/1NQaEhG4HAODLxP1sT77BL8436NKoZ3Wy1zSCyt9s5g73T1qTNxuNrExSK2Mkwfzbr/Zk8MmedG/vfuyB9pk/ndiuN83nDErh4Am3MPPcv82cA3URlWlMtkGuslR39+5dL/9O1kQREaxEXJ1u//3qKgOffH9zoziP5TVF9Zihb6mS+GYepm9/yAS14wNFFgd2a7Nh6DzYs9EULpFd5mpjHmbuN6WzafWUeL24hgufMHgQhIVUhtyCgX2RjVU0wX9whifHfiE5HqY0QejOfPg/GBBGItLLlSSoSEQUyZX81R9797uA7Mz9578uD9y5IXGMqhRsPQwTQuOdYNVkKMKWy6VEyg0XBVjuv00dezdq8MkZ/ALdGNlCLbulZA9+gjd3/X6/iNcjCXHO+VOQIc6ET3WmBeeLGIMClyfkSIIhMoVCyx6XkwpJUqo7IReupOLJ+XkxWmAQ8hXLnK0RDzVH90ufVIT/8jP0ewfeDwUShYHGDQBM3c+OP7U/f5n+vaKL8U33cNwqjV41bo2CPm5PjxvY/rTbB9uvfj9UFFGp+brQ9CpFnd6HjMp8VKsU/K1UmGTkcd1xPd+ZTLVvxyWF0cjaEVCvk0doNHN86F72i7+rhv+Vf5cWXF7m48aeZquHkTuYahqE4Vib9Mn1Pn7onOPSIMI1DfEoo+q3D+OJ43708rfZID3LxHlwJo43cPz9inralqEzcqaOdtbAHjoNpGwisYmUS8ya5A2mp8+tTCO1aV2Sr0So09ECqYhCC1IuqWbIpvXupHu5Ca2XXdMUoVSUZVT8Cgfu7GjY1kwHlqspPt67H/cpvZPU7D3TpZne1SUXCrjQezNnIPhGXjckpZkUp5LSXOkpW7MA7OPrf7ZHo9LaPfUYVj8Kiizq9nsWULamCru3u59R965nwTzm4RNG3Tc9CyKMUb9/27OAiHBJ1xh13/Vynxa7eBURB0E6BzTGld768v21W+7iD+7Xz857CHn4JDgJl5e6GCLxVqLQhZyuZcMY17r0Yny1WMJmicygziWRMHT8ASQ8Qh0UXTNRFuHzdR3QRcZYVQ0W/NfS9zApK5VS1R0BjY6XSm2SvEDIav47hsogjnwzP5QMOVOY72EH1cLJeiHUHm7C32G6eLggiq4R1iReoQQiEGRO8gIlinVWN2uTUZe5+fJOL64acnPLOZZg5wApFRekmfdCHscY6tBYIJBEFjzh1ipD8SeCZT/ROa7X+Cj//vNdoagIdHSKLy+Awbo4iFZNeXPXq8Mgid7p9FzqA2Q+F88QmsdeO/YYmsrZb8taKhNuqqL0ARHbasmFzxVj7eBVL8bajjMItQo1A2QavgV6XW/ofD1A735coH0TFMM0jwQ0etaI2wG8DucdDz7h9vQ8WdyCBWUHSme+632Ej64H3UzkTLIlKGWZbGaJoYQu0TV56yIjeyiIbPccoQwFTRUXu1cxmWPcTONmTVISzMsy0zwY1zKpgaivriBcEpVttvpHkCMj23GzZ4EJX2O26y4EX6XB75yybOPNH0mUb735U4xkjd13u+cnGj51v989poInXG/WP+i6tsrLDZnQUB10TOptWGJV1Dz81xjI0LoPZ5MpDaKVXeuoATUcnBXN8HHG1uou2ECCRxnQPLhB+3LvoEhsO7BTKb/KoGVoP7fDxUgiFHNORFRPvLILdAD1o94iWb8y60+tEeB+PB45tme66sEe+Vk9r8vkIC+Tj8Lx1twsiAyz5NFB7t5qWPMUBdHBbodprWSOMk9BiaoodkOBCTKVJV6E5dMbPYGuRVQgwyVGqxh3S//+baVlEwrOdOWfEPUeLr6Dyn8XncOGzesA1AIEL4h0IDDkTQGvymgA4XNKxbbgThlyof9ZzYtffMP2xY5BqaYeg1cNwB1nV1PDcciVNryQn6r6MjDqFe0Rbpzyak2bXLheIpSeaaekFK7rYaskqCgyWki1emUvXFf0P0JeA2+1HFXB19FRZ/tNFe4r0WDBCSSf5cS8VRrEGC1Q1JPk5AHxBRV/E7VW8dtQQVTr9gNpI4RLwhaauOpxbwmPloB4FR5eR1/Z0hVP5lJx1lAkGsHYn8JOO63BxS9e7TnDs2pMNnBtce1gVTrGGkaW0TxtQIl5YDu2BN54ZDZoplqumc2AShMiNz84dVdT1lzGEbW8iDGuaQ4/tryjMW5oTl/QGJhuecthtM3MA2KdtjPZA6rV6+Apsryyj7nEKC9PjLuAF10F3ILtDavjf/oZEvKcPxywfPm6tlnsd4v+bpq9tJ6ldxz7bW8NjExvO8i4H3j19cCJM+nec0HFQH36LQ6pZVpWk68i2ZCJp8/ae9cFlbnr8xk8dTLzW9DKvivfviffad/9/rN6368rDtr2vV/R9ZYrfW6IEpqnbP5LnwUSTOYosoOAPnsHAv9Y6fPCm2rD+23POG+dbndXL+OHfMM6w8n4yz6itWh+OCEgj3w098YjQkZVc0SmfkY4L3hEoujmHPlatIiOfK02vk8t+YS/KrdxJyTkh86/AwAA//9NtxihhCMAAA==\"") + } diff --git a/migrate/sql/20180103142001_initial_schema.sql b/migrate/sql/20180103142001_initial_schema.sql index b14d7cde2..39cb63475 100644 --- a/migrate/sql/20180103142001_initial_schema.sql +++ b/migrate/sql/20180103142001_initial_schema.sql @@ -100,6 +100,7 @@ CREATE TABLE IF NOT EXISTS storage ( UNIQUE (collection, key, user_id) ); CREATE INDEX IF NOT EXISTS collection_read_user_id_key_idx ON storage (collection, read, user_id, key); +CREATE INDEX IF NOT EXISTS value_ginidx ON storage USING GIN (value); CREATE TABLE IF NOT EXISTS message ( PRIMARY KEY (stream_mode, stream_subject, stream_descriptor, stream_label, create_time, id), @@ -170,7 +171,42 @@ CREATE TABLE IF NOT EXISTS user_tombstone ( create_time TIMESTAMPTZ DEFAULT now() NOT NULL ); +CREATE TABLE IF NOT EXISTS groups ( + PRIMARY KEY (lang_tag, edge_count, id, disable_time), + + id UUID UNIQUE NOT NULL, + creator_id UUID NOT NULL, + name VARCHAR(255) CONSTRAINT groups_name_key UNIQUE NOT NULL, + description VARCHAR(255), + avatar_url VARCHAR(255), + -- https://tools.ietf.org/html/bcp47 + lang_tag VARCHAR(18) DEFAULT 'en', + metadata JSONB DEFAULT '{}' NOT NULL, + state SMALLINT DEFAULT 0 CHECK (state >= 0) NOT NULL, -- open(0), closed(1) + edge_count INT DEFAULT 0 CHECK (edge_count >= 1 AND edge_count <= max_count) NOT NULL, + max_count INT DEFAULT 100 CHECK (max_count >= 1) NOT NULL, + create_time TIMESTAMPTZ DEFAULT now() NOT NULL, + update_time TIMESTAMPTZ DEFAULT now() NOT NULL, + disable_time TIMESTAMPTZ DEFAULT CAST(0 AS TIMESTAMPTZ) NOT NULL +); +CREATE INDEX IF NOT EXISTS edge_count_update_time_id_idx ON groups (edge_count, update_time, id, disable_time); +CREATE INDEX IF NOT EXISTS create_time_edge_count_id_idx ON groups (create_time, edge_count, id, disable_time); + +CREATE TABLE IF NOT EXISTS group_edge ( + PRIMARY KEY (source_id, state, position), + + source_id UUID NOT NULL, + position BIGINT NOT NULL, -- Used for sort order on rows. + update_time TIMESTAMPTZ DEFAULT now() NOT NULL, + destination_id UUID NOT NULL, + state SMALLINT DEFAULT 0 NOT NULL, -- superadmin(0), admin(1), member(2), join_request(3), archived(4) + + UNIQUE (source_id, destination_id) +); + -- +migrate Down +DROP TABLE IF EXISTS group_edge; +DROP TABLE IF EXISTS groups; DROP TABLE IF EXISTS user_tombstone; DROP TABLE IF EXISTS wallet_ledger; DROP TABLE IF EXISTS leaderboard_record; diff --git a/server/api_group.go b/server/api_group.go index c12e73580..73b87227b 100644 --- a/server/api_group.go +++ b/server/api_group.go @@ -15,10 +15,276 @@ package server import ( + "github.com/golang/protobuf/ptypes/empty" "github.com/heroiclabs/nakama/api" + "github.com/satori/go.uuid" "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) -func (s *ApiServer) CreateGroup(ctx context.Context, in *api.CreateGroupsRequest) (*api.Groups, error) { - return nil, nil +func (s *ApiServer) CreateGroup(ctx context.Context, in *api.CreateGroupRequest) (*api.Group, error) { + if in.GetName() == "" { + return nil, status.Error(codes.InvalidArgument, "Group name must be set.") + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + + group, err := CreateGroup(s.logger, s.db, userID, userID, in.GetName(), in.GetLangTag(), in.GetDescription(), in.GetAvatarUrl(), "", in.GetOpen(), -1) + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to create group.") + } + + if group == nil { + return nil, status.Error(codes.InvalidArgument, "Did not create group as a group already exists with the same name.") + } + + return group, nil +} + +func (s *ApiServer) UpdateGroup(ctx context.Context, in *api.UpdateGroupRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + if in.GetName() != nil { + if len(in.GetName().String()) < 1 { + return nil, status.Error(codes.InvalidArgument, "Group name cannot be empty.") + } + } + + if in.GetLangTag() != nil { + if len(in.GetLangTag().String()) < 1 { + return nil, status.Error(codes.InvalidArgument, "Group language cannot be empty.") + } + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + updated, err := UpdateGroup(s.logger, s.db, groupID, userID, nil, in.GetName(), in.GetLangTag(), in.GetDescription(), in.GetAvatarUrl(), nil, in.GetOpen(), -1) + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to update group.") + } + + if !updated { + return nil, status.Error(codes.InvalidArgument, "Did not update group - Make sure that group exists, group name is unique and you have the correct permissions.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) DeleteGroup(ctx context.Context, in *api.DeleteGroupRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + deleted, err := DeleteGroup(s.logger, s.db, groupID, userID) + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to delete group.") + } + + if !deleted { + return nil, status.Error(codes.InvalidArgument, "Did not delete group - Make sure that group exists and you have the correct permissions.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) JoinGroup(ctx context.Context, in *api.JoinGroupRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + + joined, err := JoinGroup(s.logger, s.db, groupID, userID) + + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to join group.") + } + + if !joined { + return nil, status.Error(codes.InvalidArgument, "Did not join group - Make sure that group exists and maximum count has not been reached.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) LeaveGroup(ctx context.Context, in *api.LeaveGroupRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + left, err := LeaveGroup(s.logger, s.db, groupID, userID) + + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to leave group.") + } + + if !left { + return nil, status.Error(codes.InvalidArgument, "Did not leave group - Make sure that group exists and you have the correct permissions.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) AddGroupUsers(ctx context.Context, in *api.AddGroupUsersRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + if len(in.GetUserIds()) == 0 { + return &empty.Empty{}, nil + } + + userIDs := make([]uuid.UUID, 0, len(in.GetUserIds())) + for _, id := range in.GetUserIds() { + uid := uuid.FromStringOrNil(id) + if uuid.Equal(uuid.Nil, uid) { + return nil, status.Error(codes.InvalidArgument, "User ID must be a valid ID.") + } + userIDs = append(userIDs, uid) + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + done, err := AddGroupUsers(s.logger, s.db, userID, groupID, userIDs) + + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to add users to a group.") + } + + if !done { + return nil, status.Error(codes.InvalidArgument, "Did not add users to group - Make sure that group exists, you have correct permissions, and maximum member count is not reached.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) KickGroupUsers(ctx context.Context, in *api.KickGroupUsersRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + if len(in.GetUserIds()) == 0 { + return &empty.Empty{}, nil + } + + userIDs := make([]uuid.UUID, 0, len(in.GetUserIds())) + for _, id := range in.GetUserIds() { + uid := uuid.FromStringOrNil(id) + if uuid.Equal(uuid.Nil, uid) { + return nil, status.Error(codes.InvalidArgument, "User ID must be a valid ID.") + } + userIDs = append(userIDs, uid) + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + if err = KickGroupUsers(s.logger, s.db, userID, groupID, userIDs); err != nil { + return nil, status.Error(codes.Internal, "Error while trying to kick users from a group.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) PromoteGroupUsers(ctx context.Context, in *api.PromoteGroupUsersRequest) (*empty.Empty, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + if len(in.GetUserIds()) == 0 { + return &empty.Empty{}, nil + } + + userIDs := make([]uuid.UUID, 0, len(in.GetUserIds())) + for _, id := range in.GetUserIds() { + uid := uuid.FromStringOrNil(id) + if uuid.Equal(uuid.Nil, uid) { + return nil, status.Error(codes.InvalidArgument, "User ID must be a valid ID.") + } + userIDs = append(userIDs, uid) + } + + userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID) + promoted, err := PromoteGroupUsers(s.logger, s.db, userID, groupID, userIDs) + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to promote users in a group.") + } + + if !promoted { + return nil, status.Error(codes.InvalidArgument, "Did not promote users to group - Make sure that group exists and you have correct permissions.") + } + + return &empty.Empty{}, nil +} + +func (s *ApiServer) ListGroupUsers(ctx context.Context, in *api.ListGroupUsersRequest) (*api.GroupUserList, error) { + if in.GetGroupId() == "" { + return nil, status.Error(codes.InvalidArgument, "Group ID must be set.") + } + + groupID, err := uuid.FromString(in.GetGroupId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + groupUsers, err := ListGroupUsers(s.logger, s.db, s.tracker, groupID) + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to list users in a group.") + } + + return groupUsers, nil +} + +func (s *ApiServer) ListUserGroups(ctx context.Context, in *api.ListUserGroupsRequest) (*api.UserGroupList, error) { + if in.GetUserId() == "" { + return nil, status.Error(codes.InvalidArgument, "User ID must be set.") + } + + userID, err := uuid.FromString(in.GetUserId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.") + } + + userGroups, err := ListUserGroups(s.logger, s.db, userID) + if err != nil { + return nil, status.Error(codes.Internal, "Error while trying to list groups for a user.") + } + + return userGroups, nil } diff --git a/server/api_link.go b/server/api_link.go index 9abb6e1f3..7d664c2e0 100644 --- a/server/api_link.go +++ b/server/api_link.go @@ -84,7 +84,7 @@ func (s *ApiServer) LinkDevice(ctx context.Context, in *api.AccountDevice) (*emp var dbDeviceIdLinkedUser int64 err := tx.QueryRow("SELECT COUNT(id) FROM user_device WHERE id = $1 AND user_id = $2 LIMIT 1", deviceID, userID).Scan(&dbDeviceIdLinkedUser) if err != nil { - s.logger.Error("Cannot link device ID.", zap.Error(err), zap.Any("input", in)) + s.logger.Debug("Cannot link device ID.", zap.Error(err), zap.Any("input", in)) return err } @@ -94,14 +94,14 @@ func (s *ApiServer) LinkDevice(ctx context.Context, in *api.AccountDevice) (*emp if e, ok := err.(*pq.Error); ok && e.Code == dbErrorUniqueViolation { return StatusError(codes.AlreadyExists, "Device ID already in use.", err) } - s.logger.Error("Cannot link device ID.", zap.Error(err), zap.Any("input", in)) + s.logger.Debug("Cannot link device ID.", zap.Error(err), zap.Any("input", in)) return err } } _, err = tx.Exec("UPDATE users SET update_time = now() WHERE id = $1", userID) if err != nil { - s.logger.Error("Cannot update users table while linking.", zap.Error(err), zap.Any("input", in)) + s.logger.Debug("Cannot update users table while linking.", zap.Error(err), zap.Any("input", in)) return err } return nil diff --git a/server/api_unlink.go b/server/api_unlink.go index 898bf0bbd..35486f92d 100644 --- a/server/api_unlink.go +++ b/server/api_unlink.go @@ -82,7 +82,7 @@ AND (EXISTS (SELECT id FROM users WHERE id = $1 AND res, err := tx.Exec(query, userID, in.Id) if err != nil { - s.logger.Error("Could not unlink device ID.", zap.Error(err), zap.Any("input", in)) + s.logger.Debug("Could not unlink device ID.", zap.Error(err), zap.Any("input", in)) return err } if count, _ := res.RowsAffected(); count == 0 { @@ -91,7 +91,7 @@ AND (EXISTS (SELECT id FROM users WHERE id = $1 AND res, err = tx.Exec("UPDATE users SET update_time = now() WHERE id = $1", userID) if err != nil { - s.logger.Error("Could not unlink device ID.", zap.Error(err), zap.Any("input", in)) + s.logger.Debug("Could not unlink device ID.", zap.Error(err), zap.Any("input", in)) return err } if count, _ := res.RowsAffected(); count == 0 { diff --git a/server/console_gdpr.go b/server/console_gdpr.go index ff7549d69..502319c46 100644 --- a/server/console_gdpr.go +++ b/server/console_gdpr.go @@ -75,6 +75,13 @@ func (s *ConsoleServer) ExportAccount(ctx context.Context, in *console.AccountId return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") } + // Messages. + messages, err := GetChannelMessages(s.logger, s.db, userID) + if err != nil { + s.logger.Error("Could not fetch messages", zap.Error(err), zap.String("user_id", in.Id)) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + // Leaderboard records. leaderboardRecords, err := LeaderboardRecordReadAll(s.logger, s.db, userID) if err != nil { @@ -124,11 +131,12 @@ func (s *ConsoleServer) ExportAccount(ctx context.Context, in *console.AccountId } } - // TODO(mo, zyro) add groups, chat messages + // TODO(mo, zyro) add groups export := &console.AccountExport{ Account: account, Objects: storageObjects, Friends: friends.GetFriends(), + Messages: messages, LeaderboardRecords: leaderboardRecords, Notifications: notifications.GetNotifications(), WalletLedgers: wl, diff --git a/server/core_authenticate.go b/server/core_authenticate.go index b7188661e..d153d57bb 100644 --- a/server/core_authenticate.go +++ b/server/core_authenticate.go @@ -173,24 +173,24 @@ WHERE NOT EXISTS } else if e, ok := err.(*pq.Error); ok && e.Code == dbErrorUniqueViolation && strings.Contains(e.Message, "users_username_key") { return StatusError(codes.AlreadyExists, "Username is already in use.", err) } - logger.Error("Cannot find or create user with device ID.", zap.Error(err), zap.String("deviceID", deviceID), zap.String("username", username), zap.Bool("create", create)) + logger.Debug("Cannot find or create user with device ID.", zap.Error(err), zap.String("deviceID", deviceID), zap.String("username", username), zap.Bool("create", create)) return err } if rowsAffectedCount, _ := result.RowsAffected(); rowsAffectedCount != 1 { - logger.Error("Did not insert new user.", zap.Int64("rows_affected", rowsAffectedCount)) + logger.Debug("Did not insert new user.", zap.Int64("rows_affected", rowsAffectedCount)) return StatusError(codes.Internal, "Error finding or creating user account.", ErrRowsAffectedCount) } query = "INSERT INTO user_device (id, user_id) VALUES ($1, $2)" result, err = tx.Exec(query, deviceID, userID) if err != nil { - logger.Error("Cannot add device ID.", zap.Error(err), zap.String("deviceID", deviceID), zap.String("username", username), zap.Bool("create", create)) + logger.Debug("Cannot add device ID.", zap.Error(err), zap.String("deviceID", deviceID), zap.String("username", username), zap.Bool("create", create)) return err } if rowsAffectedCount, _ := result.RowsAffected(); rowsAffectedCount != 1 { - logger.Error("Did not insert new user.", zap.Int64("rows_affected", rowsAffectedCount)) + logger.Debug("Did not insert new user.", zap.Int64("rows_affected", rowsAffectedCount)) return StatusError(codes.Internal, "Error finding or creating user account.", ErrRowsAffectedCount) } @@ -552,7 +552,6 @@ func importFacebookFriends(logger *zap.Logger, db *sql.DB, messageRouter Message return nil } - position := time.Now().UTC().Unix() friendUserIDs := make([]uuid.UUID, 0) tx, err := db.Begin() @@ -630,6 +629,7 @@ func importFacebookFriends(logger *zap.Logger, db *sql.DB, messageRouter Message var id string for rows.Next() { + position := time.Now().UTC().UnixNano() err = rows.Scan(&id) if err != nil { // Error scanning the ID, try to skip this user and move on. @@ -712,6 +712,7 @@ AND EXISTS notifications := make(map[uuid.UUID][]*api.Notification, len(friendUserIDs)) content, _ := json.Marshal(map[string]interface{}{"username": username}) subject := "Your friend has just joined the game" + createTime := time.Now().UTC().Unix() for _, friendUserID := range friendUserIDs { notifications[friendUserID] = []*api.Notification{&api.Notification{ Id: uuid.Must(uuid.NewV4()).String(), @@ -720,7 +721,7 @@ AND EXISTS SenderId: userID.String(), Code: NOTIFICATION_FRIEND_JOIN_GAME, Persistent: true, - CreateTime: ×tamp.Timestamp{Seconds: position}, + CreateTime: ×tamp.Timestamp{Seconds: createTime}, }} } NotificationSend(logger, db, messageRouter, notifications) diff --git a/server/core_channel.go b/server/core_channel.go index 5c66700bb..3e283dd53 100644 --- a/server/core_channel.go +++ b/server/core_channel.go @@ -204,6 +204,60 @@ WHERE stream_mode = $1 AND stream_subject = $2::UUID AND stream_descriptor = $3: }, nil } +func GetChannelMessages(logger *zap.Logger, db *sql.DB, userID uuid.UUID) ([]*api.ChannelMessage, error) { + query := "SELECT id, code, username, stream_mode, stream_subject, stream_descriptor, stream_label, content, create_time, update_time FROM message WHERE sender_id = $1::UUID" + rows, err := db.Query(query, userID) + if err != nil { + logger.Error("Error listing channel messages for user", zap.String("user_id", userID.String()), zap.Error(err)) + return nil, err + } + defer rows.Close() + + messages := make([]*api.ChannelMessage, 0, 100) + var dbId string + var dbCode int32 + var dbUsername string + var dbStreamMode uint8 + var dbStreamSubject string + var dbStreamDescriptor string + var dbStreamLabel string + var dbContent string + var dbCreateTime pq.NullTime + var dbUpdateTime pq.NullTime + for rows.Next() { + err = rows.Scan(&dbId, &dbCode, &dbUsername, &dbStreamMode, &dbStreamSubject, &dbStreamDescriptor, &dbStreamLabel, &dbContent, &dbCreateTime, &dbUpdateTime) + if err != nil { + logger.Error("Error parsing listed channel messages for user", zap.String("user_id", userID.String()), zap.Error(err)) + return nil, err + } + + channelId, err := StreamToChannelId(PresenceStream{ + Mode: dbStreamMode, + Subject: uuid.FromStringOrNil(dbStreamSubject), + Descriptor: uuid.FromStringOrNil(dbStreamDescriptor), + Label: dbStreamLabel, + }) + if err != nil { + logger.Error("Error processing listed channel messages for user", zap.String("user_id", userID.String()), zap.Error(err)) + return nil, err + } + + messages = append(messages, &api.ChannelMessage{ + ChannelId: channelId, + MessageId: dbId, + Code: &wrappers.Int32Value{Value: dbCode}, + SenderId: userID.String(), + Username: dbUsername, + Content: dbContent, + CreateTime: ×tamp.Timestamp{Seconds: dbCreateTime.Time.Unix()}, + UpdateTime: ×tamp.Timestamp{Seconds: dbUpdateTime.Time.Unix()}, + Persistent: &wrappers.BoolValue{Value: true}, + }) + } + + return messages, nil +} + func ChannelIdToStream(channelId string) (*ChannelIdToStreamResult, error) { if channelId == "" { return nil, ErrChannelIdInvalid diff --git a/server/core_friend.go b/server/core_friend.go index 996c7f212..73642fc9a 100644 --- a/server/core_friend.go +++ b/server/core_friend.go @@ -164,6 +164,7 @@ func AddFriends(logger *zap.Logger, db *sql.DB, messageRouter MessageRouter, use } return nil }); err != nil { + logger.Error("Error adding friends.", zap.Error(err)) return err } @@ -201,7 +202,7 @@ func addFriend(logger *zap.Logger, tx *sql.Tx, userID uuid.UUID, friendID string logger.Info("Ignoring previously blocked friend. Delete friend first before attempting to add.", zap.String("user", userID.String()), zap.String("friend", friendID)) return false, sql.ErrNoRows } - logger.Error("Failed to check edge state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to check edge state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return false, err } @@ -212,17 +213,17 @@ WHERE (source_id = $1 AND destination_id = $2 AND state = 2) OR (source_id = $2 AND destination_id = $1 AND state = 1) `, friendID, userID) if err != nil { - logger.Error("Failed to update user state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return false, err } // If both edges were updated, it was accepting an invite was successful. if rowsAffected, _ := res.RowsAffected(); rowsAffected == 2 { - logger.Info("Accepting friend invitation.", zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Accepting friend invitation.", zap.String("user", userID.String()), zap.String("friend", friendID)) return true, nil } - position := time.Now().UTC().Unix() + position := time.Now().UTC().UnixNano() // If no edge updates took place, it's either a new invite being set up, or user was blocked off by friend. _, err = tx.Exec(` @@ -243,7 +244,7 @@ WHERE ON CONFLICT (source_id, destination_id) DO NOTHING `, userID, friendID, position) if err != nil { - logger.Error("Failed to insert new user edge link.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to insert new user edge link.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return false, err } @@ -265,17 +266,17 @@ AND EXISTS (source_id = $2::UUID AND destination_id = $1::UUID AND position = $3::BIGINT) ) `, userID, friendID, position); err != nil { - logger.Error("Failed to update user count.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user count.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return false, err } // An invite was successfully added if both components were inserted. if rowsAffected, _ := res.RowsAffected(); rowsAffected != 2 { - logger.Info("Did not add new friend as friend connection already exists or user is blocked.", zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Did not add new friend as friend connection already exists or user is blocked.", zap.String("user", userID.String()), zap.String("friend", friendID)) return false, sql.ErrNoRows } - logger.Info("Added new friend invitation.", zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Added new friend invitation.", zap.String("user", userID.String()), zap.String("friend", friendID)) return false, nil } @@ -291,38 +292,43 @@ func DeleteFriends(logger *zap.Logger, db *sql.DB, currentUser uuid.UUID, ids [] return err } - return crdb.ExecuteInTx(context.Background(), tx, func() error { + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { for id := range uniqueFriendIDs { if deleteFriendErr := deleteFriend(logger, tx, currentUser, id); deleteFriendErr != nil { return deleteFriendErr } } return nil - }) + }); err != nil { + logger.Error("Error deleting friends.", zap.Error(err)) + return err + } + + return nil } func deleteFriend(logger *zap.Logger, tx *sql.Tx, userID uuid.UUID, friendID string) error { res, err := tx.Exec("DELETE FROM user_edge WHERE (source_id = $1 AND destination_id = $2) OR (source_id = $2 AND destination_id = $1 AND state <> 3)", userID, friendID) if err != nil { - logger.Error("Failed to delete user edge relationships.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to delete user edge relationships.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - logger.Info("Could not delete user relationships as prior relationship did not exist.", zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Could not delete user relationships as prior relationship did not exist.", zap.String("user", userID.String()), zap.String("friend", friendID)) return nil } else if rowsAffected == 1 { if _, err = tx.Exec("UPDATE users SET edge_count = edge_count - 1, update_time = now() WHERE id = $1::UUID", userID); err != nil { - logger.Error("Failed to update user edge counts.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user edge counts.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } } else if rowsAffected == 2 { if _, err = tx.Exec("UPDATE users SET edge_count = edge_count - 1, update_time = now() WHERE id IN ($1, $2)", userID, friendID); err != nil { - logger.Error("Failed to update user edge counts.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user edge counts.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } } else { - logger.Error("Unexpected number of edges were deleted.", zap.String("user", userID.String()), zap.String("friend", friendID), zap.Int64("rows_affected", rowsAffected)) + logger.Debug("Unexpected number of edges were deleted.", zap.String("user", userID.String()), zap.String("friend", friendID), zap.Int64("rows_affected", rowsAffected)) return errors.New("unexpected number of edges were deleted") } @@ -341,14 +347,19 @@ func BlockFriends(logger *zap.Logger, db *sql.DB, currentUser uuid.UUID, ids []s return err } - return crdb.ExecuteInTx(context.Background(), tx, func() error { + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { for id := range uniqueFriendIDs { if blockFriendErr := blockFriend(logger, tx, currentUser, id); blockFriendErr != nil { return blockFriendErr } } return nil - }) + }); err != nil { + logger.Error("Error blocking friends.", zap.Error(err)) + return err + } + + return nil } func blockFriend(logger *zap.Logger, tx *sql.Tx, userID uuid.UUID, friendID string) error { @@ -357,11 +368,11 @@ func blockFriend(logger *zap.Logger, tx *sql.Tx, userID uuid.UUID, friendID stri userID, friendID) if err != nil { - logger.Error("Failed to update user edge state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user edge state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } - position := time.Now().UTC().Unix() + position := time.Now().UTC().UnixNano() if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { // If there was no previous edge then create one. query := ` @@ -373,18 +384,18 @@ FROM (VALUES WHERE EXISTS (SELECT id FROM users WHERE id = $2::UUID)` res, err = tx.Exec(query, userID, friendID, position) if err != nil { - logger.Error("Failed to block user.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to block user.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - logger.Info("Could not block user as user may not exist.", zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Could not block user as user may not exist.", zap.String("user", userID.String()), zap.String("friend", friendID)) return nil } // Update the edge count. if _, err = tx.Exec("UPDATE users SET edge_count = edge_count + 1, update_time = now() WHERE id = $1", userID); err != nil { - logger.Error("Failed to update user edge count.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user edge count.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } } @@ -392,13 +403,13 @@ WHERE EXISTS (SELECT id FROM users WHERE id = $2::UUID)` // Delete opposite relationship if user hasn't blocked you already res, err = tx.Exec("DELETE FROM user_edge WHERE source_id = $1 AND destination_id = $2 AND state != 3", friendID, userID) if err != nil { - logger.Error("Failed to update user edge state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user edge state.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 1 { if _, err = tx.Exec("UPDATE users SET edge_count = edge_count - 1, update_time = now() WHERE id = $1", friendID); err != nil { - logger.Error("Failed to update user edge count.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) + logger.Debug("Failed to update user edge count.", zap.Error(err), zap.String("user", userID.String()), zap.String("friend", friendID)) return err } } diff --git a/server/core_group.go b/server/core_group.go new file mode 100644 index 000000000..90ceb1a93 --- /dev/null +++ b/server/core_group.go @@ -0,0 +1,956 @@ +// Copyright 2018 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "context" + "database/sql" + "strconv" + "strings" + "time" + + "github.com/cockroachdb/cockroach-go/crdb" + "github.com/golang/protobuf/ptypes/timestamp" + "github.com/golang/protobuf/ptypes/wrappers" + "github.com/heroiclabs/nakama/api" + "github.com/lib/pq" + "github.com/pkg/errors" + "github.com/satori/go.uuid" + "go.uber.org/zap" +) + +func CreateGroup(logger *zap.Logger, db *sql.DB, userID uuid.UUID, creatorID uuid.UUID, name, lang, desc, avatarURL, metadata string, open bool, maxCount int) (*api.Group, error) { + if uuid.Equal(uuid.Nil, userID) { + logger.Panic("This function must be used with non-system user ID.") + } + + state := 1 + if open { + state = 0 + } + + params := []interface{}{uuid.Must(uuid.NewV4()), creatorID, name, desc, avatarURL, state} + query := ` +INSERT INTO groups + (id, creator_id, name, description, avatar_url, state, edge_count) +VALUES + ($1, $2, $3, $4, $5, $6, 1) +RETURNING id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata, create_time, update_time` + if lang != "" { + params = append(params, lang) + query = ` +INSERT INTO groups + (id, creator_id, name, description, avatar_url, state, edge_count, lang_tag) +VALUES + ($1, $2, $3, $4, $5, $6, 1, $7) +RETURNING id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata, create_time, update_time` + } + + // called from the client + if maxCount > 0 && metadata != "" { + params = append(params, maxCount, metadata) //no need to add 'lang' again + + if lang != "" { + query = ` +INSERT INTO groups + (id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata) +VALUES + ($1, $2, $3, $4, $5, $6, 1, $7, $8, $9) +RETURNING id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata, create_time, update_time` + } else { + query = ` +INSERT INTO groups + (id, creator_id, name, description, avatar_url, state, edge_count, max_count, metadata) +VALUES + ($1, $2, $3, $4, $5, $6, 1, $7, $8) +RETURNING id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata, create_time, update_time` + } + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return nil, err + } + + var group *api.Group + duplicateGroup := false + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { + rows, err := tx.Query(query, params...) + if err != nil { + logger.Debug("Could not create group.", zap.Error(err)) + return err + } + + groups, err := groupConvertRows(rows) + if err != nil { + if e, ok := err.(*pq.Error); ok && e.Code == dbErrorUniqueViolation { + logger.Info("Could not create group as it already exists.", zap.String("name", name)) + duplicateGroup = true + return nil + } + logger.Debug("Could not parse rows.", zap.Error(err)) + return err + } + + group = groups[0] + _, err = groupAddUser(db, tx, uuid.Must(uuid.FromString(group.Id)), userID, 0) + if err != nil { + logger.Debug("Could not add user to group.", zap.Error(err)) + return err + } + + return nil + }); err != nil { + if duplicateGroup { + return nil, nil + } + logger.Error("Error creating group.", zap.Error(err)) + return nil, err + } + + return group, nil +} + +func UpdateGroup(logger *zap.Logger, db *sql.DB, groupID uuid.UUID, userID uuid.UUID, creatorID []byte, name, lang, desc, avatar, metadata *wrappers.StringValue, open *wrappers.BoolValue, maxCount int) (bool, error) { + if !uuid.Equal(uuid.Nil, userID) { + allowedUser, err := groupCheckUserPermission(logger, db, groupID, userID, 1) + if err != nil { + return false, err + } + + if !allowedUser { + logger.Info("User does not have permission to update group.", zap.String("group", groupID.String()), zap.String("user", userID.String())) + return false, nil + } + } + + statements := make([]string, 0) + params := []interface{}{groupID} + index := 2 + + if name != nil { + statements = append(statements, "name = $"+strconv.Itoa(index)) + params = append(params, name.GetValue()) + index++ + } + + if lang != nil { + statements = append(statements, "lang = $"+strconv.Itoa(index)) + params = append(params, lang.GetValue()) + index++ + } + + if desc != nil { + if u := desc.GetValue(); u == "" { + statements = append(statements, "description = NULL") + } else { + statements = append(statements, "description = $"+strconv.Itoa(index)) + params = append(params, u) + index++ + } + } + + if avatar != nil { + if u := avatar.GetValue(); u == "" { + statements = append(statements, "avatar_url = NULL") + } else { + statements = append(statements, "avatar_url = $"+strconv.Itoa(index)) + params = append(params, u) + index++ + } + } + + if open != nil { + state := 0 + if !open.GetValue() { + state = 1 + } + statements = append(statements, "state = $"+strconv.Itoa(index)) + params = append(params, state) + index++ + } + + if metadata != nil { + statements = append(statements, "metadata = $"+strconv.Itoa(index)) + params = append(params, metadata.GetValue()) + index++ + } + + if maxCount >= 1 { + statements = append(statements, "max_count = $"+strconv.Itoa(index)) + params = append(params, maxCount) + index++ + } + + if creatorID != nil { + statements = append(statements, "creator_id = $"+strconv.Itoa(index)+"::UUID") + params = append(params, creatorID) + index++ + } + + if len(statements) == 0 { + logger.Info("Did not update group as no fields were changed.") + return false, nil + } + + query := "UPDATE groups SET update_time = now(), " + strings.Join(statements, ", ") + " WHERE id = $1" + res, err := db.Exec(query, params...) + if err != nil { + if e, ok := err.(*pq.Error); ok && e.Code == dbErrorUniqueViolation { + logger.Info("Could not update group as it already exists.", zap.String("group_id", groupID.String())) + return false, nil + } + logger.Error("Could not update group.", zap.Error(err)) + return false, err + } + + if rowsAffected, err := res.RowsAffected(); err != nil { + logger.Error("Could not get rows affected after group update query.", zap.Error(err)) + return false, err + } else { + if rowsAffected == 0 { + return false, nil + } + + return true, nil + } +} + +func DeleteGroup(logger *zap.Logger, db *sql.DB, groupID uuid.UUID, userID uuid.UUID) (bool, error) { + if !uuid.Equal(uuid.Nil, userID) { + // only super-admins can delete group. + allowedUser, err := groupCheckUserPermission(logger, db, groupID, userID, 0) + if err != nil { + return false, err + } + + if !allowedUser { + logger.Info("User does not have permission to delete group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return false, nil + } + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return false, err + } + + deletedGroup := true + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { + query := "DELETE FROM groups WHERE id = $1::UUID" + res, err := tx.Exec(query, groupID) + if err != nil { + logger.Debug("Could not delete group.", zap.Error(err)) + return err + } + + if rowsAffected, err := res.RowsAffected(); err != nil { + logger.Debug("Could not count deleted groups.", zap.Error(err)) + return err + } else if rowsAffected == 0 { + logger.Info("Did not delete group as group with given ID does not exist.", zap.Error(err), zap.String("group_id", groupID.String())) + deletedGroup = false + return nil + } + + query = "DELETE FROM group_edge WHERE source_id = $1::UUID OR destination_id = $1::UUID" + if _, err = tx.Exec(query, groupID); err != nil { + logger.Debug("Could not delete group_edge relationships.", zap.Error(err)) + return err + } + + return nil + }); err != nil { + logger.Error("Error deleting group.", zap.Error(err)) + return false, err + } + + return deletedGroup, nil +} + +func JoinGroup(logger *zap.Logger, db *sql.DB, groupID uuid.UUID, userID uuid.UUID) (bool, error) { + query := ` +SELECT id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata, create_time, update_time +FROM groups +WHERE id = $1` + rows, err := db.Query(query, groupID) + if err != nil { + if err == sql.ErrNoRows { + logger.Info("Group does not exist.", zap.Error(err), zap.String("group_id", groupID.String())) + return false, nil + } + logger.Error("Could not look up group while trying to join it.", zap.Error(err)) + return false, err + } + + groups, err := groupConvertRows(rows) + if err != nil { + logger.Error("Could not parse groups.", zap.Error(err)) + return false, err + } + + group := groups[0] + if group.EdgeCount >= group.MaxCount { + logger.Info("Group maximum count has reached.", zap.Error(err), zap.String("group_id", groupID.String())) + return false, nil + } + + state := 2 + if !group.Open.Value { + state = 3 + _, err = groupAddUser(db, nil, uuid.Must(uuid.FromString(group.Id)), userID, state) + if err != nil { + if e, ok := err.(*pq.Error); ok && e.Code == dbErrorUniqueViolation { + logger.Info("Could not add user to group as relationship already exists.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return true, nil // completed successfully + } + + logger.Error("Could not add user to group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return false, err + } + + logger.Info("Added join request to group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return true, nil + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return false, err + } + + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { + _, err = groupAddUser(db, tx, uuid.Must(uuid.FromString(group.Id)), userID, state) + if err != nil { + if e, ok := err.(*pq.Error); ok && e.Code == dbErrorUniqueViolation { + logger.Info("Could not add user to group as relationship already exists.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return nil // completed successfully + } + + logger.Debug("Could not add user to group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return err + } + + query = "UPDATE groups SET edge_count = edge_count + 1, update_time = now() WHERE id = $1::UUID AND edge_count+1 <= max_count" + _, err := tx.Exec(query, groupID) + if err != nil { + logger.Debug("Could not update group edge_count.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return err + } + + return nil + }); err != nil { + logger.Error("Error joining group.", zap.Error(err)) + return false, err + } + + logger.Info("Successfully joined group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return true, nil +} + +func LeaveGroup(logger *zap.Logger, db *sql.DB, groupID uuid.UUID, userID uuid.UUID) (bool, error) { + var myState sql.NullInt64 + query := "SELECT state FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID" + if err := db.QueryRow(query, groupID, userID).Scan(&myState); err != nil { + if err == sql.ErrNoRows { + logger.Info("Could not retrieve state as no group relationship exists.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return true, nil //completed successfully + } + logger.Error("Could not retrieve state from group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return false, err + } + + if myState.Int64 == 0 { + // check for other superadmins + var otherSuperadminCount sql.NullInt64 + query := "SELECT COUNT(destination_id) FROM group_edge WHERE source_id = $1::UUID AND destination_id != $2::UUID AND state = 0" + if err := db.QueryRow(query, groupID, userID).Scan(&otherSuperadminCount); err != nil { + logger.Error("Could not look up superadmin count group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return false, err + } + + if otherSuperadminCount.Int64 == 0 { + logger.Info("Cannot leave group as user is last superadmin.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return false, nil + } + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return false, err + } + + if err := crdb.ExecuteInTx(context.Background(), tx, func() error { + query = "DELETE FROM group_edge WHERE (source_id = $1::UUID AND destination_id = $2::UUID) OR (source_id = $2::UUID AND destination_id = $1::UUID)" + // don't need to check affectedRows as we've confirmed the existence of the relationship above + if _, err = tx.Exec(query, groupID, userID); err != nil { + logger.Debug("Could not delete group_edge relationships.", zap.Error(err)) + return err + } + + // check to ensure we are not decrementing the count when the relationship was an invite. + if myState.Int64 < 3 { + query = "UPDATE groups SET edge_count = edge_count - 1, update_time = now() WHERE id = $1::UUID" + _, err = tx.Exec(query, groupID) + if err != nil { + logger.Debug("Could not update group edge_count.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return err + } + } + return nil + }); err != nil { + logger.Error("Error leaving group.", zap.Error(err)) + return false, err + } + + logger.Info("Successfully left group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return true, nil +} + +func AddGroupUsers(logger *zap.Logger, db *sql.DB, caller uuid.UUID, groupID uuid.UUID, userIDs []uuid.UUID) (bool, error) { + if !uuid.Equal(uuid.Nil, caller) { + var dbState sql.NullInt64 + query := "SELECT state FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID" + if err := db.QueryRow(query, groupID, caller).Scan(&dbState); err != nil { + if err == sql.ErrNoRows { + logger.Info("Could not retrieve state as no group relationship exists.", zap.String("group_id", groupID.String()), zap.String("user_id", caller.String())) + return false, nil + } + logger.Error("Could not retrieve state from group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", caller.String())) + return false, err + } + + if dbState.Int64 > 1 { + logger.Info("Cannot add users as user does not have correct permissions.", zap.String("group_id", groupID.String()), zap.String("user_id", caller.String()), zap.Int64("state", dbState.Int64)) + return false, nil + } + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return false, err + } + + maxCountReached := false + if err := crdb.ExecuteInTx(context.Background(), tx, func() error { + for _, uid := range userIDs { + if uuid.Equal(caller, uid) { + continue + } + + incrementEdgeCount := true + var userExists sql.NullBool + query := "SELECT EXISTS(SELECT 1 FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID)" + if err := tx.QueryRow(query, groupID, uid).Scan(&userExists); err != nil { + logger.Debug("Could not retrieve user state from group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + + if !userExists.Bool { + if _, err = groupAddUser(db, tx, groupID, uid, 2); err != nil { + logger.Debug("Could not add user to group.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + } else { + res, err := groupUpdateUserState(db, tx, groupID, uid, 3, 2) + if err != nil { + logger.Debug("Could not update user state in group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + if res != 2 { + incrementEdgeCount = false + } + } + + if incrementEdgeCount { + query = "UPDATE groups SET edge_count = edge_count + 1, update_time = now() WHERE id = $1::UUID AND edge_count+1 <= max_count" + res, err := tx.Exec(query, groupID) + if err != nil { + logger.Debug("Could not update group edge_count.", zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + + if rowsAffected, err := res.RowsAffected(); err != nil { + logger.Debug("Could not update group edge_count.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } else if rowsAffected == 0 { + logger.Info("Could not add users as group maximum count was reached.", zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + maxCountReached = true + return errors.New("Could not add users as group maximum count was reached.") + } + } + } + return nil + }); err != nil { + if !maxCountReached { + logger.Error("Error adding users to group.", zap.Error(err)) + return false, err + } else { + return false, nil + } + } + + return true, err +} + +func KickGroupUsers(logger *zap.Logger, db *sql.DB, caller uuid.UUID, groupID uuid.UUID, userIDs []uuid.UUID) error { + myState := 0 + if !uuid.Equal(uuid.Nil, caller) { + var dbState sql.NullInt64 + query := "SELECT state FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID" + if err := db.QueryRow(query, groupID, caller).Scan(&dbState); err != nil { + if err == sql.ErrNoRows { + logger.Info("Could not retrieve state as no group relationship exists.", zap.String("group_id", groupID.String()), zap.String("user_id", caller.String())) + return err + } + logger.Error("Could not retrieve state from group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", caller.String())) + return err + } + + myState = int(dbState.Int64) + if myState > 1 { + logger.Info("Cannot kick users as user does not have correct permissions.", zap.String("group_id", groupID.String()), zap.String("user_id", caller.String()), zap.Int("state", myState)) + return errors.New("Cannot kick users as user does not have correct permissions.") + } + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return err + } + + if err := crdb.ExecuteInTx(context.Background(), tx, func() error { + for _, uid := range userIDs { + // shouldn't kick self + if uuid.Equal(caller, uid) { + continue + } + + params := []interface{}{groupID, uid} + query := "" + if myState == 0 { + // ensure we aren't removing the last superadmin when deleting authoritatively. + // query is for superadmin or if done authoritatively + query = ` +DELETE FROM group_edge +WHERE + ( + (source_id = $1::UUID AND destination_id = $2::UUID) + OR + (source_id = $2::UUID AND destination_id = $1::UUID) + ) +AND + EXISTS (SELECT id FROM groups WHERE id = $1 AND disable_time::INT = 0) +AND + NOT ( + (EXISTS (SELECT 1 FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID AND state = 0)) + AND + ((SELECT COUNT(destination_id) FROM group_edge WHERE (source_id = $1::UUID AND destination_id != $2::UUID AND state = 0)) = 0) + ) +RETURNING state` + } else { + // query is just for admins + query = ` +DELETE FROM group_edge +WHERE + ( + (source_id = $1::UUID AND destination_id = $2::UUID AND state > 1) + OR + (source_id = $2::UUID AND destination_id = $1::UUID AND state > 1) + ) +AND + EXISTS (SELECT id FROM groups WHERE id = $1 AND disable_time::INT = 0) +RETURNING state` + } + + var deletedState sql.NullInt64 + logger.Debug("Kick user from group query.", zap.String("query", query), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String()), zap.String("caller", caller.String()), zap.Int("caller_state", myState)) + if err := tx.QueryRow(query, params...).Scan(&deletedState); err != nil { + if err == sql.ErrNoRows { + // ignore - move to the next uid + continue + } else { + logger.Debug("Could not delete relationship from group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + } + + // make sure that we kicked valid members, not invites + if deletedState.Int64 < 3 { + query = "UPDATE groups SET edge_count = edge_count - 1, update_time = now() WHERE id = $1::UUID" + _, err = tx.Exec(query, groupID) + if err != nil { + logger.Debug("Could not update group edge_count.", zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + } + } + return nil + }); err != nil { + logger.Error("Error kicking users from group.", zap.Error(err)) + return err + } + + return nil +} + +func PromoteGroupUsers(logger *zap.Logger, db *sql.DB, caller uuid.UUID, groupID uuid.UUID, userIDs []uuid.UUID) (bool, error) { + myState := 0 + if !uuid.Equal(uuid.Nil, caller) { + var dbState sql.NullInt64 + query := "SELECT state FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID" + if err := db.QueryRow(query, groupID, caller).Scan(&dbState); err != nil { + if err == sql.ErrNoRows { + logger.Info("Could not retrieve state as no group relationship exists.", zap.String("group_id", groupID.String()), zap.String("user_id", caller.String())) + return false, nil + } + logger.Error("Could not retrieve state from group_edge.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", caller.String())) + return false, err + } + + myState = int(dbState.Int64) + if myState > 1 { + logger.Info("Cannot promote users as user does not have correct permissions.", zap.String("group_id", groupID.String()), zap.String("user_id", caller.String()), zap.Int("state", myState)) + return false, nil + } + } + + tx, err := db.Begin() + if err != nil { + logger.Error("Could not begin database transaction.", zap.Error(err)) + return false, err + } + + if err := crdb.ExecuteInTx(context.Background(), tx, func() error { + for _, uid := range userIDs { + if uuid.Equal(caller, uid) { + continue + } + + query := ` +UPDATE group_edge SET state = state - 1 +WHERE + (source_id = $1::UUID AND destination_id = $2::UUID AND state > 0 AND state > $3) +OR + (source_id = $2::UUID AND destination_id = $1::UUID AND state > 0 AND state > $3) +RETURNING state` + + var newState sql.NullInt64 + if err := tx.QueryRow(query, groupID, uid, myState).Scan(&newState); err != nil { + if err == sql.ErrNoRows { + return nil + } + logger.Debug("Could not promote user in group.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", uid.String())) + return err + } + + if newState.Int64 == 2 { + query = "UPDATE groups SET edge_count = edge_count + 1, update_time = now() WHERE id = $1::UUID AND edge_count+1 <= max_count" + _, err := tx.Exec(query, groupID) + if err != nil { + logger.Debug("Could not update group edge_count.", zap.String("group_id", groupID.String())) + return err + } + } + } + return nil + }); err != nil { + logger.Error("Error promote users from group.", zap.Error(err)) + return false, err + } + + return true, nil +} + +func ListGroupUsers(logger *zap.Logger, db *sql.DB, tracker Tracker, groupID uuid.UUID) (*api.GroupUserList, error) { + query := ` +SELECT u.id, u.username, u.display_name, u.avatar_url, + u.lang_tag, u.location, u.timezone, u.metadata, + u.facebook_id, u.google_id, u.gamecenter_id, u.steam_id, u.edge_count, + u.create_time, u.update_time, ge.state +FROM users u, group_edge ge +WHERE u.id = ge.source_id AND ge.destination_id = $1` + + rows, err := db.Query(query, groupID) + if err != nil { + logger.Debug("Could not list users in group.", zap.Error(err), zap.String("group_id", groupID.String())) + return nil, err + } + defer rows.Close() + + groupUsers := make([]*api.GroupUserList_GroupUser, 0) + for rows.Next() { + var id string + var displayName sql.NullString + var username sql.NullString + var avatarURL sql.NullString + var langTag sql.NullString + var location sql.NullString + var timezone sql.NullString + var metadata []byte + var facebook sql.NullString + var google sql.NullString + var gamecenter sql.NullString + var steam sql.NullString + var edgeCount int + var createTime pq.NullTime + var updateTime pq.NullTime + var state sql.NullInt64 + + if err := rows.Scan(&id, &username, &displayName, &avatarURL, &langTag, &location, &timezone, &metadata, + &facebook, &google, &gamecenter, &steam, &edgeCount, &createTime, &updateTime, &state); err != nil { + logger.Error("Could not parse rows when listing users in a group.", zap.Error(err), zap.String("group_id", groupID.String())) + return nil, err + } + + userID := uuid.Must(uuid.FromString(id)) + user := &api.User{ + Id: userID.String(), + Username: username.String, + DisplayName: displayName.String, + AvatarUrl: avatarURL.String, + LangTag: langTag.String, + Location: location.String, + Timezone: timezone.String, + Metadata: string(metadata), + FacebookId: facebook.String, + GoogleId: google.String, + GamecenterId: gamecenter.String, + SteamId: steam.String, + EdgeCount: int32(edgeCount), + CreateTime: ×tamp.Timestamp{Seconds: createTime.Time.Unix()}, + UpdateTime: ×tamp.Timestamp{Seconds: updateTime.Time.Unix()}, + Online: tracker.StreamExists(PresenceStream{Mode: StreamModeNotifications, Subject: userID}), + } + + groupUser := &api.GroupUserList_GroupUser{User: user} + switch state.Int64 { + case 0: + groupUser.State = int32(api.GroupUserList_GroupUser_SUPERADMIN) + case 1: + groupUser.State = int32(api.GroupUserList_GroupUser_ADMIN) + case 2: + groupUser.State = int32(api.GroupUserList_GroupUser_MEMBER) + case 3: + groupUser.State = int32(api.GroupUserList_GroupUser_JOIN_REQUEST) + } + groupUsers = append(groupUsers, groupUser) + } + + return &api.GroupUserList{GroupUsers: groupUsers}, nil +} + +func ListUserGroups(logger *zap.Logger, db *sql.DB, userID uuid.UUID) (*api.UserGroupList, error) { + query := ` +SELECT id, creator_id, name, description, avatar_url, +lang_tag, metadata, groups.state, edge_count, max_count, +create_time, groups.update_time, group_edge.state +FROM groups +JOIN group_edge ON (group_edge.source_id = id) +WHERE group_edge.destination_id = $1 AND disable_time::INT = 0` + + rows, err := db.Query(query, userID) + if err != nil { + logger.Debug("Could not list groups for a user.", zap.Error(err), zap.String("user_id", userID.String())) + return nil, err + } + defer rows.Close() + + userGroups := make([]*api.UserGroupList_UserGroup, 0) + for rows.Next() { + var id string + var creatorID sql.NullString + var name sql.NullString + var description sql.NullString + var avatarURL sql.NullString + var lang sql.NullString + var metadata []byte + var state sql.NullInt64 + var edgeCount sql.NullInt64 + var maxCount sql.NullInt64 + var createTime pq.NullTime + var updateTime pq.NullTime + var userState sql.NullInt64 + + if err := rows.Scan(&id, &creatorID, &name, + &description, &avatarURL, &lang, &metadata, &state, + &edgeCount, &maxCount, &createTime, &updateTime, &userState); err != nil { + logger.Error("Could not parse rows when listing groups for a user.", zap.Error(err), zap.String("user_id", userID.String())) + return nil, err + } + + open := true + if state.Int64 == 1 { + open = false + } + + group := &api.Group{ + Id: uuid.Must(uuid.FromString(id)).String(), + CreatorId: uuid.Must(uuid.FromString(creatorID.String)).String(), + Name: name.String, + Description: description.String, + AvatarUrl: avatarURL.String, + LangTag: lang.String, + Metadata: string(metadata), + Open: &wrappers.BoolValue{Value: open}, + EdgeCount: int32(edgeCount.Int64), + MaxCount: int32(maxCount.Int64), + CreateTime: ×tamp.Timestamp{Seconds: createTime.Time.Unix()}, + UpdateTime: ×tamp.Timestamp{Seconds: updateTime.Time.Unix()}, + } + + userGroup := &api.UserGroupList_UserGroup{Group: group} + switch userState.Int64 { + case 0: + userGroup.State = int32(api.UserGroupList_UserGroup_SUPERADMIN) + case 1: + userGroup.State = int32(api.UserGroupList_UserGroup_ADMIN) + case 2: + userGroup.State = int32(api.UserGroupList_UserGroup_MEMBER) + case 3: + userGroup.State = int32(api.UserGroupList_UserGroup_JOIN_REQUEST) + } + + userGroups = append(userGroups, userGroup) + } + + return &api.UserGroupList{UserGroups: userGroups}, nil + +} + +func groupConvertRows(rows *sql.Rows) ([]*api.Group, error) { + defer rows.Close() + + groups := make([]*api.Group, 0) + + for rows.Next() { + var id string + var creatorID sql.NullString + var name sql.NullString + var description sql.NullString + var avatarURL sql.NullString + var lang sql.NullString + var metadata []byte + var state sql.NullInt64 + var edgeCount sql.NullInt64 + var maxCount sql.NullInt64 + var createTime pq.NullTime + var updateTime pq.NullTime + + if err := rows.Scan(&id, &creatorID, &name, &description, &avatarURL, &state, &edgeCount, &lang, &maxCount, &metadata, &createTime, &updateTime); err != nil { + return nil, err + } + + open := true + if state.Int64 == 1 { + open = false + } + + group := &api.Group{ + Id: uuid.Must(uuid.FromString(id)).String(), + CreatorId: uuid.Must(uuid.FromString(creatorID.String)).String(), + Name: name.String, + Description: description.String, + AvatarUrl: avatarURL.String, + LangTag: lang.String, + Metadata: string(metadata), + Open: &wrappers.BoolValue{Value: open}, + EdgeCount: int32(edgeCount.Int64), + MaxCount: int32(maxCount.Int64), + CreateTime: ×tamp.Timestamp{Seconds: createTime.Time.Unix()}, + UpdateTime: ×tamp.Timestamp{Seconds: updateTime.Time.Unix()}, + } + + groups = append(groups, group) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return groups, nil +} + +func groupAddUser(db *sql.DB, tx *sql.Tx, groupID uuid.UUID, userID uuid.UUID, state int) (int64, error) { + query := ` +INSERT INTO group_edge + (position, state, source_id, destination_id) +VALUES + ($1, $2, $3, $4), + ($1, $2, $4, $3)` + + position := time.Now().UTC().UnixNano() + + var res sql.Result + var err error + if tx != nil { + res, err = tx.Exec(query, position, state, groupID, userID) + } else { + res, err = db.Exec(query, position, state, groupID, userID) + } + + if err != nil { + return 0, err + } + + if rowsAffected, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return rowsAffected, nil + } +} + +func groupUpdateUserState(db *sql.DB, tx *sql.Tx, groupID uuid.UUID, userID uuid.UUID, fromState int, toState int) (int64, error) { + query := ` +UPDATE group_edge SET + update_time = now(), state = $4 +WHERE + (source_id = $1::UUID AND destination_id = $2::UUID AND state = $3) +OR + (source_id = $2::UUID AND destination_id = $1::UUID AND state = $3)` + + var res sql.Result + var err error + if tx != nil { + res, err = tx.Exec(query, groupID, userID, fromState, toState) + } else { + res, err = db.Exec(query, groupID, userID, fromState, toState) + } + + if err != nil { + return 0, err + } + + if rowsAffected, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return rowsAffected, nil + } +} + +func groupCheckUserPermission(logger *zap.Logger, db *sql.DB, groupID, userID uuid.UUID, state int) (bool, error) { + var allowedUser sql.NullBool + query := "SELECT EXISTS (SELECT 1 FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID AND state > -1 AND state <= $3)" + if err := db.QueryRow(query, groupID, userID, state).Scan(&allowedUser); err != nil { + logger.Error("Could not look up user state with group.", zap.Error(err), zap.String("group_id", groupID.String()), zap.String("user_id", userID.String())) + return false, err + } + + return allowedUser.Bool, nil +} diff --git a/server/core_notification.go b/server/core_notification.go index ebe7ea664..48fac2c25 100644 --- a/server/core_notification.go +++ b/server/core_notification.go @@ -154,7 +154,7 @@ ORDER BY create_time ASC`+limitQuery, params...) func NotificationDelete(logger *zap.Logger, db *sql.DB, userID uuid.UUID, notificationIDs []string) error { statements := make([]string, 0, len(notificationIDs)) - params := make([]interface{}, 0, len(notificationIDs)) + params := make([]interface{}, 0, len(notificationIDs)+1) params = append(params, userID) for _, id := range notificationIDs { diff --git a/server/core_storage.go b/server/core_storage.go index 966c75266..cdd8af810 100644 --- a/server/core_storage.go +++ b/server/core_storage.go @@ -344,7 +344,6 @@ WHERE } func StorageWriteObjects(logger *zap.Logger, db *sql.DB, authoritativeWrite bool, objects map[uuid.UUID][]*api.WriteStorageObject) (*api.StorageObjectAcks, codes.Code, error) { - returnCode := codes.Internal acks := &api.StorageObjectAcks{} tx, err := db.Begin() @@ -353,18 +352,17 @@ func StorageWriteObjects(logger *zap.Logger, db *sql.DB, authoritativeWrite bool return nil, codes.Internal, err } - if err := crdb.ExecuteInTx(context.Background(), tx, func() error { + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { for ownerID, userObjects := range objects { for _, object := range userObjects { ack, writeErr := storageWriteObject(logger, tx, authoritativeWrite, ownerID, object) if writeErr != nil { if writeErr == sql.ErrNoRows { - returnCode = codes.InvalidArgument - return errors.New("Storage write rejected - not found, version check failed, or permission denied.") + return StatusError(codes.InvalidArgument, "Storage write rejected.", errors.New("Storage write rejected - not found, version check failed, or permission denied.")) } - returnCode = codes.Internal - return writeErr + logger.Debug("Error writing storage objects.", zap.Error(err)) + return err } acks.Acks = append(acks.Acks, ack) @@ -372,7 +370,11 @@ func StorageWriteObjects(logger *zap.Logger, db *sql.DB, authoritativeWrite bool } return nil }); err != nil { - return nil, returnCode, err + if e, ok := err.(*statusError); ok { + return nil, e.Code(), e.Cause() + } + logger.Error("Error writing storage objects.", zap.Error(err)) + return nil, codes.Internal, err } return acks, codes.OK, nil @@ -399,7 +401,7 @@ func storageWriteObject(logger *zap.Logger, tx *sql.Tx, authoritativeWrite bool, if err := tx.QueryRow(query, params...).Scan(&ack.Collection, &ack.Key, &ack.Version); err != nil { if err != sql.ErrNoRows { - logger.Error("Could not write storage object.", zap.Error(err), zap.String("query", query), zap.Any("object", object)) + logger.Debug("Could not write storage object.", zap.Error(err), zap.String("query", query), zap.Any("object", object)) } return nil, err @@ -491,8 +493,6 @@ RETURNING collection, key, version` } func StorageDeleteObjects(logger *zap.Logger, db *sql.DB, authoritativeDelete bool, userObjectIDs map[uuid.UUID][]*api.DeleteStorageObjectId) (codes.Code, error) { - returnCode := codes.Internal - tx, err := db.Begin() if err != nil { logger.Error("Could not begin database transaction.", zap.Error(err)) @@ -516,20 +516,22 @@ func StorageDeleteObjects(logger *zap.Logger, db *sql.DB, authoritativeDelete bo result, err := tx.Exec(query, params...) if err != nil { - returnCode = codes.Internal - logger.Error("Could not delete storage object.", zap.Error(err), zap.String("query", query), zap.Any("object_id", objectID)) + logger.Debug("Could not delete storage object.", zap.Error(err), zap.String("query", query), zap.Any("object_id", objectID)) return err } if rowsAffected, _ := result.RowsAffected(); rowsAffected == 0 { - returnCode = codes.InvalidArgument - return errors.New("Storage delete rejected - not found, version check failed, or permission denied.") + return StatusError(codes.InvalidArgument, "Storage delete rejected.", errors.New("Storage delete rejected - not found, version check failed, or permission denied.")) } } } return nil }); err != nil { - return returnCode, err + if e, ok := err.(*statusError); ok { + return e.Code(), e.Cause() + } + logger.Error("Error deleting storage objects.", zap.Error(err)) + return codes.Internal, err } return codes.OK, nil diff --git a/server/core_wallet.go b/server/core_wallet.go index 8ba40357f..96b246f10 100644 --- a/server/core_wallet.go +++ b/server/core_wallet.go @@ -55,20 +55,20 @@ func UpdateWallets(logger *zap.Logger, db *sql.DB, updates []*walletUpdate) erro return err } - return crdb.ExecuteInTx(context.Background(), tx, func() error { + if err = crdb.ExecuteInTx(context.Background(), tx, func() error { for _, update := range updates { var wallet sql.NullString query := "SELECT wallet FROM users WHERE id = $1::UUID" err := tx.QueryRow(query, update.UserID).Scan(&wallet) if err != nil { - logger.Error("Error retrieving user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) + logger.Debug("Error retrieving user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) return err } var walletMap map[string]interface{} err = json.Unmarshal([]byte(wallet.String), &walletMap) if err != nil { - logger.Error("Error converting current user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) + logger.Debug("Error converting current user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) return err } @@ -80,32 +80,37 @@ func UpdateWallets(logger *zap.Logger, db *sql.DB, updates []*walletUpdate) erro walletData, err := json.Marshal(walletMap) if err != nil { - logger.Error("Error converting new user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) + logger.Debug("Error converting new user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) return err } query = "UPDATE users SET update_time = now(), wallet = $2 WHERE id = $1::UUID" _, err = tx.Exec(query, update.UserID, string(walletData)) if err != nil { - logger.Error("Error writing user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) + logger.Debug("Error writing user wallet.", zap.String("user_id", update.UserID.String()), zap.Error(err)) return err } changesetData, err := json.Marshal(update.Changeset) if err != nil { - logger.Error("Error converting new user wallet changeset.", zap.String("user_id", update.UserID.String()), zap.Error(err)) + logger.Debug("Error converting new user wallet changeset.", zap.String("user_id", update.UserID.String()), zap.Error(err)) return err } query = "INSERT INTO wallet_ledger (id, user_id, changeset, metadata) VALUES ($1::UUID, $2::UUID, $3, $4)" _, err = tx.Exec(query, uuid.Must(uuid.NewV4()), update.UserID, changesetData, update.Metadata) if err != nil { - logger.Error("Error writing user wallet ledger.", zap.String("user_id", update.UserID.String()), zap.Error(err)) + logger.Debug("Error writing user wallet ledger.", zap.String("user_id", update.UserID.String()), zap.Error(err)) return err } } return nil - }) + }); err != nil { + logger.Error("Error updating wallets.", zap.Error(err)) + return err + } + + return nil } func UpdateWalletLedger(logger *zap.Logger, db *sql.DB, id uuid.UUID, metadata string) (*walletLedger, error) { diff --git a/server/db.go b/server/db.go index 35804a008..5068f0879 100644 --- a/server/db.go +++ b/server/db.go @@ -28,6 +28,7 @@ var ErrRowsAffectedCount = errors.New("rows_affected_count") // A type that wraps an outgoing client-facing error together with an underlying cause error. type statusError struct { + code codes.Code status error cause error } @@ -46,9 +47,14 @@ func (s *statusError) Status() error { return s.status } +func (s *statusError) Code() codes.Code { + return s.code +} + // Helper function for creating status errors that wrap underlying causes, usually DB errors. func StatusError(code codes.Code, msg string, cause error) error { return &statusError{ + code: code, status: status.Error(code, msg), cause: cause, } diff --git a/server/logger.go b/server/logger.go index 73f3575e6..fc30383b5 100644 --- a/server/logger.go +++ b/server/logger.go @@ -19,11 +19,26 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" + "strings" ) -func SetupLogging(config Config) (*zap.Logger, *zap.Logger) { - consoleLogger := NewJSONLogger(os.Stdout, config.GetLogger().Level) - fileLogger := NewJSONFileLogger(consoleLogger, config.GetLogger().File, config.GetLogger().Level) +func SetupLogging(tmpLogger *zap.Logger, config Config) (*zap.Logger, *zap.Logger) { + zapLevel := zapcore.InfoLevel + switch strings.ToLower(config.GetLogger().Level) { + case "debug": + zapLevel = zapcore.DebugLevel + case "info": + zapLevel = zapcore.InfoLevel + case "warn": + zapLevel = zapcore.WarnLevel + case "error": + zapLevel = zapcore.ErrorLevel + default: + tmpLogger.Fatal("Logger level invalid, must be one of: DEBUG, INFO, WARN, or ERROR") + } + + consoleLogger := NewJSONLogger(os.Stdout, zapLevel) + fileLogger := NewJSONFileLogger(consoleLogger, config.GetLogger().File, zapLevel) if fileLogger != nil { multiLogger := NewMultiLogger(consoleLogger, fileLogger) @@ -41,7 +56,7 @@ func SetupLogging(config Config) (*zap.Logger, *zap.Logger) { return consoleLogger, consoleLogger } -func NewJSONFileLogger(consoleLogger *zap.Logger, fpath string, level string) *zap.Logger { +func NewJSONFileLogger(consoleLogger *zap.Logger, fpath string, level zapcore.Level) *zap.Logger { if len(fpath) == 0 { return nil } @@ -66,7 +81,7 @@ func NewMultiLogger(loggers ...*zap.Logger) *zap.Logger { return zap.New(teeCore, options...) } -func NewJSONLogger(output *os.File, level string) *zap.Logger { +func NewJSONLogger(output *os.File, level zapcore.Level) *zap.Logger { jsonEncoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", @@ -80,21 +95,7 @@ func NewJSONLogger(output *os.File, level string) *zap.Logger { EncodeCaller: zapcore.ShortCallerEncoder, }) - zapLevel := zapcore.InfoLevel - switch string(level) { - case "debug", "DEBUG": - zapLevel = zapcore.DebugLevel - case "info", "INFO": - zapLevel = zapcore.InfoLevel - case "warn", "WARN": - zapLevel = zapcore.WarnLevel - case "error", "ERROR": - zapLevel = zapcore.ErrorLevel - default: - zapLevel = zapcore.InfoLevel - } - - core := zapcore.NewCore(jsonEncoder, zapcore.Lock(output), zapLevel) + core := zapcore.NewCore(jsonEncoder, zapcore.Lock(output), level) options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)} return zap.New(core, options...) } diff --git a/server/runtime_nakama_module.go b/server/runtime_nakama_module.go index b12976049..c0fd2af3a 100644 --- a/server/runtime_nakama_module.go +++ b/server/runtime_nakama_module.go @@ -169,6 +169,11 @@ func (n *NakamaModule) Loader(l *lua.LState) int { "leaderboard_records_list": n.leaderboardRecordsList, "leaderboard_record_write": n.leaderboardRecordWrite, "leaderboard_record_delete": n.leaderboardRecordDelete, + "group_create": n.groupCreate, + "group_update": n.groupUpdate, + "group_delete": n.groupDelete, + "group_users_list": n.groupUsersList, + "user_groups_list": n.userGroupsList, } mod := l.SetFuncs(l.CreateTable(0, len(functions)), functions) @@ -2315,7 +2320,7 @@ func (n *NakamaModule) notificationsSend(l *lua.LState) int { } sid, err := uuid.FromString(u) if err != nil { - l.ArgError(1, "expects user_id to be a valid UUID") + l.ArgError(1, "expects sender_id to be a valid UUID") return } senderID = sid @@ -2327,16 +2332,16 @@ func (n *NakamaModule) notificationsSend(l *lua.LState) int { } if notification.Subject == "" { - l.ArgError(1, "expects subject to be non-empty") + l.ArgError(1, "expects subject to be provided and to be non-empty") return } else if len(notification.Content) == 0 { - l.ArgError(1, "expects content to be a valid JSON") + l.ArgError(1, "expects content to be provided and be valid JSON") return } else if uuid.Equal(uuid.Nil, userID) { - l.ArgError(1, "expects user_id to be a valid UUID") + l.ArgError(1, "expects user_id to be provided and be a valid UUID") return } else if notification.Code == 0 { - l.ArgError(1, "expects code to number above 0") + l.ArgError(1, "expects code to be provided and be a number above 0") return } @@ -3391,3 +3396,293 @@ func (n *NakamaModule) leaderboardRecordDelete(l *lua.LState) int { } return 0 } + +func (n *NakamaModule) groupCreate(l *lua.LState) int { + userID, err := uuid.FromString(l.CheckString(1)) + if err != nil { + l.ArgError(1, "expects user ID to be a valid identifier") + return 0 + } + + name := l.CheckString(2) + if name == "" { + l.ArgError(2, "expects group name not be empty") + return 0 + } + + creatorID, err := uuid.FromString(l.OptString(3, uuid.Nil.String())) + if err != nil { + l.ArgError(3, "expects owner ID to be a valid identifier") + return 0 + } + + lang := l.OptString(4, "") + desc := l.OptString(5, "") + avatarURL := l.OptString(6, "") + open := l.OptBool(7, false) + metadata := l.OptTable(8, nil) + metadataStr := "" + if metadata != nil { + metadataMap := ConvertLuaTable(metadata) + metadataBytes, err := json.Marshal(metadataMap) + if err != nil { + l.RaiseError("error encoding metadata: %v", err.Error()) + return 0 + } + metadataStr = string(metadataBytes) + } + maxCount := l.OptInt(9, 0) + if maxCount < 0 || maxCount > 100 { + l.ArgError(9, "expects max_count to be > 0 and <= 100") + return 0 + } + + group, err := CreateGroup(n.logger, n.db, userID, creatorID, name, lang, desc, avatarURL, metadataStr, open, maxCount) + if err != nil { + l.RaiseError("error while trying to create group: %v", err.Error()) + return 0 + } + + if group == nil { + l.RaiseError("did not create group as a group already exists with the same name") + return 0 + } + + groupTable := l.CreateTable(0, 12) + groupTable.RawSetString("id", lua.LString(group.Id)) + groupTable.RawSetString("creator_id", lua.LString(group.CreatorId)) + groupTable.RawSetString("name", lua.LString(group.Name)) + groupTable.RawSetString("description", lua.LString(group.Description)) + groupTable.RawSetString("avatar_url", lua.LString(group.AvatarUrl)) + groupTable.RawSetString("lang_tag", lua.LString(group.LangTag)) + + metadataMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(group.Metadata), &metadataMap) + if err != nil { + l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) + return 0 + } + metadataTable := ConvertMap(l, metadataMap) + groupTable.RawSetString("metadata", metadataTable) + groupTable.RawSetString("open", lua.LBool(group.Open.Value)) + groupTable.RawSetString("edge_count", lua.LNumber(group.EdgeCount)) + groupTable.RawSetString("max_count", lua.LNumber(group.MaxCount)) + groupTable.RawSetString("create_time", lua.LNumber(group.CreateTime.Seconds)) + groupTable.RawSetString("update_time", lua.LNumber(group.UpdateTime.Seconds)) + + l.Push(groupTable) + return 1 +} + +func (n *NakamaModule) groupUpdate(l *lua.LState) int { + groupID, err := uuid.FromString(l.CheckString(1)) + if err != nil { + l.ArgError(1, "expects group ID to be a valid identifier") + return 0 + } + + nameStr := l.OptString(2, "") + var name *wrappers.StringValue + if nameStr != "" { + name = &wrappers.StringValue{Value: nameStr} + } + + creatorIDStr := l.OptString(3, "") + var creatorID []byte + if creatorIDStr != "" { + cuid, err := uuid.FromString(creatorIDStr) + if err != nil { + l.ArgError(3, "expects creator ID to be a valid identifier") + return 0 + } + creatorID = cuid.Bytes() + } + + langStr := l.OptString(4, "") + var lang *wrappers.StringValue + if langStr != "" { + lang = &wrappers.StringValue{Value: langStr} + } + + descStr := l.OptString(5, "") + var desc *wrappers.StringValue + if descStr != "" { + desc = &wrappers.StringValue{Value: descStr} + } + + avatarURLStr := l.OptString(6, "") + var avatarURL *wrappers.StringValue + if avatarURLStr != "" { + avatarURL = &wrappers.StringValue{Value: avatarURLStr} + } + + openV := l.Get(7) + var open *wrappers.BoolValue + if openV != lua.LNil { + open = &wrappers.BoolValue{Value: l.OptBool(7, false)} + } + + metadataTable := l.OptTable(8, nil) + var metadata *wrappers.StringValue + if metadataTable != nil { + metadataMap := ConvertLuaTable(metadataTable) + metadataBytes, err := json.Marshal(metadataMap) + if err != nil { + l.RaiseError("error encoding metadata: %v", err.Error()) + return 0 + } + metadata = &wrappers.StringValue{Value: string(metadataBytes)} + } + + maxCountInt := l.OptInt(9, 0) + maxCount := 0 + if maxCountInt > 0 && maxCountInt <= 100 { + maxCount = maxCountInt + } + + updated, err := UpdateGroup(n.logger, n.db, groupID, uuid.Nil, creatorID, name, lang, desc, avatarURL, metadata, open, maxCount) + if err != nil { + l.RaiseError("error while trying to update group: %v", err.Error()) + return 0 + } + + if !updated { + l.RaiseError("did not update group - make sure group exists and name is unique") + return 0 + } + + return 0 +} + +func (n *NakamaModule) groupDelete(l *lua.LState) int { + groupID, err := uuid.FromString(l.CheckString(1)) + if err != nil { + l.ArgError(1, "expects group ID to be a valid identifier") + return 0 + } + + deleted, err := DeleteGroup(n.logger, n.db, groupID, uuid.Nil) + if err != nil { + l.RaiseError("error while trying to delete group: %v", err.Error()) + return 0 + } + + if !deleted { + l.RaiseError("did not delete group - make sure group exists.") + return 0 + } + + return 0 +} + +func (n *NakamaModule) groupUsersList(l *lua.LState) int { + groupID, err := uuid.FromString(l.CheckString(1)) + if err != nil { + l.ArgError(1, "expects group ID to be a valid identifier") + return 0 + } + + res, err := ListGroupUsers(n.logger, n.db, n.tracker, groupID) + if err != nil { + l.RaiseError("error while trying to list users in a group: %v", err.Error()) + return 0 + } + + groupUsers := l.CreateTable(len(res.GroupUsers), 0) + for i, ug := range res.GroupUsers { + u := ug.User + + ut := l.CreateTable(0, 16) + ut.RawSetString("user_id", lua.LString(u.Id)) + ut.RawSetString("username", lua.LString(u.Username)) + ut.RawSetString("display_name", lua.LString(u.DisplayName)) + ut.RawSetString("avatar_url", lua.LString(u.AvatarUrl)) + ut.RawSetString("lang_tag", lua.LString(u.LangTag)) + ut.RawSetString("location", lua.LString(u.Location)) + ut.RawSetString("timezone", lua.LString(u.Timezone)) + if u.FacebookId != "" { + ut.RawSetString("facebook_id", lua.LString(u.FacebookId)) + } + if u.GoogleId != "" { + ut.RawSetString("google_id", lua.LString(u.GoogleId)) + } + if u.GamecenterId != "" { + ut.RawSetString("gamecenter_id", lua.LString(u.GamecenterId)) + } + if u.SteamId != "" { + ut.RawSetString("steam_id", lua.LString(u.SteamId)) + } + ut.RawSetString("online", lua.LBool(u.Online)) + ut.RawSetString("edge_count", lua.LNumber(u.EdgeCount)) + ut.RawSetString("create_time", lua.LNumber(u.CreateTime.Seconds)) + ut.RawSetString("update_time", lua.LNumber(u.UpdateTime.Seconds)) + + metadataMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(u.Metadata), &metadataMap) + if err != nil { + l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) + return 0 + } + metadataTable := ConvertMap(l, metadataMap) + ut.RawSetString("metadata", metadataTable) + + gt := l.CreateTable(0, 2) + ut.RawSetString("user", ut) + ut.RawSetString("state", lua.LNumber(ug.State)) + + groupUsers.RawSetInt(i+1, gt) + } + + l.Push(groupUsers) + return 1 +} + +func (n *NakamaModule) userGroupsList(l *lua.LState) int { + userID, err := uuid.FromString(l.CheckString(1)) + if err != nil { + l.ArgError(1, "expects user ID to be a valid identifier") + return 0 + } + + res, err := ListUserGroups(n.logger, n.db, userID) + if err != nil { + l.RaiseError("error while trying to list groups for a user: %v", err.Error()) + return 0 + } + + userGroups := l.CreateTable(len(res.UserGroups), 0) + for i, ug := range res.UserGroups { + g := ug.Group + + gt := l.CreateTable(0, 12) + gt.RawSetString("id", lua.LString(g.Id)) + gt.RawSetString("creator_id", lua.LString(g.CreatorId)) + gt.RawSetString("name", lua.LString(g.Name)) + gt.RawSetString("description", lua.LString(g.Description)) + gt.RawSetString("avatar_url", lua.LString(g.AvatarUrl)) + gt.RawSetString("lang_tag", lua.LString(g.LangTag)) + gt.RawSetString("open", lua.LBool(g.Open.Value)) + gt.RawSetString("edge_count", lua.LNumber(g.EdgeCount)) + gt.RawSetString("max_count", lua.LNumber(g.MaxCount)) + gt.RawSetString("create_time", lua.LNumber(g.CreateTime.Seconds)) + gt.RawSetString("update_time", lua.LNumber(g.UpdateTime.Seconds)) + + metadataMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(g.Metadata), &metadataMap) + if err != nil { + l.RaiseError(fmt.Sprintf("failed to convert metadata to json: %s", err.Error())) + return 0 + } + metadataTable := ConvertMap(l, metadataMap) + gt.RawSetString("metadata", metadataTable) + + ugt := l.CreateTable(0, 2) + ugt.RawSetString("group", gt) + ugt.RawSetString("state", lua.LNumber(ug.State)) + + userGroups.RawSetInt(i+1, ugt) + } + + l.Push(userGroups) + return 1 +} diff --git a/tests/runtime_test.go b/tests/runtime_test.go index b01946453..372381520 100644 --- a/tests/runtime_test.go +++ b/tests/runtime_test.go @@ -51,7 +51,7 @@ func vm(t *testing.T, modules *sync.Map) *server.RuntimePool { t.Fatalf("Failed initializing runtime modules: %s", err.Error()) } - return server.NewRuntimePool(logger, logger, db, config, nil, nil, nil, nil, nil, router, stdLibs, modules, regCallbacks, once) + return server.NewRuntimePool(logger, db, config, nil, nil, nil, nil, nil, router, stdLibs, modules, regCallbacks, once) } func writeLuaModule(modules *sync.Map, name, content string) { @@ -195,10 +195,10 @@ nakama.register_rpc(test.printWorld, "helloworld") r := rp.Get() defer r.Stop() - fn := r.GetCallback(server.RPC, "helloworld") + fn := r.GetCallback(server.ExecutionModeRPC, "helloworld") payload := "Hello World" - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", payload) + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", payload) if err != nil { t.Error(err) } @@ -230,8 +230,8 @@ nakama.register_rpc(test.printWorld, "helloworld") rp := vm(t, modules) db := NewDB(t) - pipeline := server.NewPipeline(config, db, jsonpbMarshaler, jsonpbUnmarshaler, nil, nil, nil, nil, rp) - apiServer := server.StartApiServer(logger, logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, nil, nil, nil, nil, nil, nil, pipeline, rp) + pipeline := server.NewPipeline(config, db, jsonpbMarshaler, jsonpbUnmarshaler, nil, nil, nil, nil, nil, rp) + apiServer := server.StartApiServer(logger, logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, nil, nil, nil, nil, nil, nil, nil, pipeline, rp) defer apiServer.Stop() payload := "\"Hello World\"" @@ -269,8 +269,8 @@ nakama.register_rpc(test, "test") r := rp.Get() defer r.Stop() - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", "") + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", "") if err != nil { t.Error(err) } @@ -295,8 +295,8 @@ nakama.register_rpc(test, "test") defer r.Stop() payload := "{\"key\":\"value\"}" - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", payload) + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", payload) if err != nil { t.Error(err) } @@ -321,8 +321,8 @@ nakama.register_rpc(test, "test") defer r.Stop() payload := "{\"key\":\"value\"}" - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", payload) + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", payload) if err != nil { t.Error(err) } @@ -347,8 +347,8 @@ nakama.register_rpc(test, "test") defer r.Stop() payload := "{\"key\":\"value\"}" - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", payload) + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", payload) if err != nil { t.Error(err) } @@ -373,8 +373,8 @@ nakama.register_rpc(test, "test") defer r.Stop() payload := "{\"key\":\"value\"}" - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", payload) + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", payload) if err != nil { t.Error(err) } @@ -399,8 +399,8 @@ nakama.register_rpc(test, "test") defer r.Stop() payload := "{\"key\":\"value\"}" - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", payload) + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", payload) if err != nil { t.Error(err) } @@ -427,8 +427,8 @@ nakama.register_rpc(test, "test") payload := "something_to_encrypt" hash, _ := bcrypt.GenerateFromPassword([]byte(payload), bcrypt.DefaultCost) - fn := r.GetCallback(server.RPC, "test") - m, err, _ := r.InvokeFunction(server.RPC, fn, "", "", 0, "", string(hash)) + fn := r.GetCallback(server.ExecutionModeRPC, "test") + m, err, _ := r.InvokeFunction(server.ExecutionModeRPC, fn, "", "", 0, "", string(hash)) if err != nil { t.Error(err) } @@ -757,3 +757,39 @@ nakama.register_rt_after(after_match_create, "MatchCreate") t.Fatalf("Unexpected wallet value: %s", account.Wallet) } } + +func TestRuntimeGroupTests(t *testing.T) { + modules := new(sync.Map) + writeLuaModule(modules, "test.lua", ` +local nk = require("nakama") + +local user_id = nk.uuid_v4() +local group_name = nk.uuid_v4() +local group_update_name = nk.uuid_v4() + +local group = nk.group_create(user_id, group_name) +assert(not (group.id == nil or group.id == ''), "'group.id' must not be nil") +assert((group.name == group_name), "'group.name' must be set") + +nk.group_update(group.id, group_update_name) + +local users = nk.group_users_list(group.id) +for i, u in ipairs(users) +do + assert(u.user.id == user_id, "'u.id' must be equal to user_id") + assert(u.state == 1, "'u.state' must be equal to 1 / superadmin") +end + +local groups = nk.user_groups_list(user_id) +for i, g in ipairs(groups) +do + print(nk.json_encode(g)) + assert(g.group.name == group_update_name, "'g.name' must be equal to group_update_name") + assert(g.state == 1, "'g.state' must be equal to 1 / superadmin") +end + +nk.group_delete(group.id) +`) + + vm(t, modules) +} diff --git a/tests/util.go b/tests/util.go index 2aa662529..5839f4225 100644 --- a/tests/util.go +++ b/tests/util.go @@ -38,7 +38,7 @@ import ( ) var ( - logger = server.NewConsoleLogger(os.Stdout, true) + logger = NewConsoleLogger(os.Stdout, true) config = server.NewConfig(logger) jsonpbMarshaler = &jsonpb.Marshaler{ EnumsAsInts: true, @@ -150,8 +150,8 @@ func NewAPIServer(t *testing.T, runtimePool *server.RuntimePool) (*server.ApiSer db := NewDB(t) router := &DummyMessageRouter{} tracker := &server.LocalTracker{} - pipeline := server.NewPipeline(config, db, jsonpbMarshaler, jsonpbUnmarshaler, nil, nil, tracker, router, runtimePool) - apiServer := server.StartApiServer(logger, logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, nil, nil, nil, nil, tracker, router, pipeline, runtimePool) + pipeline := server.NewPipeline(config, db, jsonpbMarshaler, jsonpbUnmarshaler, nil, nil, nil, tracker, router, runtimePool) + apiServer := server.StartApiServer(logger, logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, nil, nil, nil, nil, nil, tracker, router, pipeline, runtimePool) return apiServer, pipeline } -- GitLab