Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new runtime function to get a list of user's friend status #1286

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project are documented below.
The format is based on [keep a changelog](http://keepachangelog.com) and this project uses [semantic versioning](http://semver.org).

## [Unreleased]
### Added
- Add new runtime function to get a list of user's friend status.

### Changed
- Increase limit of runtime friend listing operations to 1,000.

Expand Down
87 changes: 87 additions & 0 deletions server/core_friend.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,93 @@ FROM users, user_edge WHERE id = destination_id AND source_id = $1`
return &api.FriendList{Friends: friends}, nil
}

func GetFriends(ctx context.Context, logger *zap.Logger, db *sql.DB, statusRegistry StatusRegistry, userID uuid.UUID, userIDs []uuid.UUID) ([]*api.Friend, error) {
if len(userIDs) == 0 {
return []*api.Friend{}, nil
}

query := `
SELECT id, username, display_name, avatar_url,
lang_tag, location, timezone, metadata,
create_time, users.update_time, user_edge.update_time, state, position,
facebook_id, google_id, gamecenter_id, steam_id, facebook_instant_game_id, apple_id
FROM users, user_edge WHERE id = destination_id AND source_id = $1 AND destination_id IN $2`
rows, err := db.QueryContext(ctx, query, userID, userIDs)
if err != nil {
logger.Error("Error retrieving friends.", zap.Error(err))
return nil, err
}
defer rows.Close()

friends := make([]*api.Friend, 0, len(userIDs))
for rows.Next() {
var id string
var username sql.NullString
var displayName sql.NullString
var avatarURL sql.NullString
var lang sql.NullString
var location sql.NullString
var timezone sql.NullString
var metadata []byte
var createTime pgtype.Timestamptz
var updateTime pgtype.Timestamptz
var edgeUpdateTime pgtype.Timestamptz
var state sql.NullInt64
var position sql.NullInt64
var facebookID sql.NullString
var googleID sql.NullString
var gamecenterID sql.NullString
var steamID sql.NullString
var facebookInstantGameID sql.NullString
var appleID sql.NullString

if err = rows.Scan(&id, &username, &displayName, &avatarURL, &lang, &location, &timezone, &metadata,
&createTime, &updateTime, &edgeUpdateTime, &state, &position,
&facebookID, &googleID, &gamecenterID, &steamID, &facebookInstantGameID, &appleID); err != nil {
logger.Error("Error retrieving friends.", zap.Error(err))
return nil, err
}

user := &api.User{
Id: id,
Username: username.String,
DisplayName: displayName.String,
AvatarUrl: avatarURL.String,
LangTag: lang.String,
Location: location.String,
Timezone: timezone.String,
Metadata: string(metadata),
CreateTime: &timestamppb.Timestamp{Seconds: createTime.Time.Unix()},
UpdateTime: &timestamppb.Timestamp{Seconds: updateTime.Time.Unix()},
// Online filled below.
FacebookId: facebookID.String,
GoogleId: googleID.String,
GamecenterId: gamecenterID.String,
SteamId: steamID.String,
FacebookInstantGameId: facebookInstantGameID.String,
AppleId: appleID.String,
}

friends = append(friends, &api.Friend{
User: user,
State: &wrapperspb.Int32Value{
Value: int32(state.Int64),
},
UpdateTime: &timestamppb.Timestamp{Seconds: edgeUpdateTime.Time.Unix()},
})
}
if err = rows.Err(); err != nil {
logger.Error("Error retrieving friends.", zap.Error(err))
return nil, err
}

if statusRegistry != nil {
statusRegistry.FillOnlineFriends(friends)
}

return friends, nil
}

func ListFriends(ctx context.Context, logger *zap.Logger, db *sql.DB, statusRegistry StatusRegistry, userID uuid.UUID, limit int, state *wrapperspb.Int32Value, cursor string) (*api.FriendList, error) {
var incomingCursor *edgeListCursor
if cursor != "" {
Expand Down
25 changes: 25 additions & 0 deletions server/runtime_go_nakama.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,31 @@ func (n *RuntimeGoNakamaModule) UsersGetUsername(ctx context.Context, usernames
return users.Users, nil
}

// @group users
// @summary Get user's friend status information for a list of target users.
// @param ctx(type=context.Context) The context object represents information about the server and requester.
// @param userID (type=string) The current user ID.
// @param userIDs(type=[]string) An array of target user IDs.
// @return friends([]*api.Friend) A list of user friends objects.
// @return error(error) An optional error value if an error occurred.
func (n *RuntimeGoNakamaModule) UsersGetFriendStatus(ctx context.Context, userID string, userIDs []string) ([]*api.Friend, error) {
uid, err := uuid.FromString(userID)
if err != nil {
return nil, errors.New("expects user ID to be a valid identifier")
}

fids := make([]uuid.UUID, 0, len(userIDs))
for _, id := range userIDs {
fid, err := uuid.FromString(id)
if err != nil {
return nil, errors.New("expects user ID to be a valid identifier")
}
fids = append(fids, fid)
}

return GetFriends(ctx, n.logger, n.db, n.statusRegistry, uid, fids)
}

// @group users
// @summary Fetch one or more users randomly.
// @param ctx(type=context.Context) The context object represents information about the server and requester.
Expand Down
56 changes: 56 additions & 0 deletions server/runtime_javascript_nakama.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func (n *runtimeJavascriptNakamaModule) mappings(r *goja.Runtime) map[string]fun
"accountExportId": n.accountExportId(r),
"usersGetId": n.usersGetId(r),
"usersGetUsername": n.usersGetUsername(r),
"usersGetFriendStatus": n.usersGetFriendStatus(r),
"usersGetRandom": n.usersGetRandom(r),
"usersBanId": n.usersBanId(r),
"usersUnbanId": n.usersUnbanId(r),
Expand Down Expand Up @@ -2166,6 +2167,61 @@ func (n *runtimeJavascriptNakamaModule) usersGetUsername(r *goja.Runtime) func(g
}
}

// @group users
// @summary Get user's friend status information for a list of target users.
// @param userID (type=string) The current user ID.
// @param userIDs(type=string[]) An array of target user IDs.
// @return friends(nkruntime.Friend[]) A list of user friends objects.
// @return error(error) An optional error value if an error occurred.
func (n *runtimeJavascriptNakamaModule) usersGetFriendStatus(r *goja.Runtime) func(goja.FunctionCall) goja.Value {
return func(f goja.FunctionCall) goja.Value {
id := getJsString(r, f.Argument(0))

uid, err := uuid.FromString(id)
if err != nil {
panic(r.NewTypeError("invalid user id"))
}

ids := f.Argument(1)

uids, err := exportToSlice[[]string](ids)
if err != nil {
panic(r.NewTypeError("expects an array of strings"))
}

fids := make([]uuid.UUID, 0, len(uids))
for _, id := range uids {
fid, err := uuid.FromString(id)
if err != nil {
panic(r.NewTypeError("invalid user id"))
}
fids = append(fids, fid)
}

friends, err := GetFriends(n.ctx, n.logger, n.db, n.statusRegistry, uid, fids)
if err != nil {
panic(r.NewGoError(fmt.Errorf("failed to get user friends status: %s", err.Error())))
}

userFriends := make([]interface{}, 0, len(friends))
for _, f := range friends {
fum, err := userToJsObject(f.User)
if err != nil {
panic(r.NewGoError(err))
}

fm := make(map[string]interface{}, 3)
fm["state"] = f.State.Value
fm["updateTime"] = f.UpdateTime.Seconds
fm["user"] = fum

userFriends = append(userFriends, fm)
}

return r.ToValue(userFriends)
}
}

// @group users
// @summary Fetch one or more users randomly.
// @param count(type=number) The number of users to fetch.
Expand Down
66 changes: 66 additions & 0 deletions server/runtime_lua_nakama.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int {
"account_export_id": n.accountExportId,
"users_get_id": n.usersGetId,
"users_get_username": n.usersGetUsername,
"users_get_friend_status": n.usersGetFriendStatus,
"users_get_random": n.usersGetRandom,
"users_ban_id": n.usersBanId,
"users_unban_id": n.usersUnbanId,
Expand Down Expand Up @@ -2916,6 +2917,71 @@ func (n *RuntimeLuaNakamaModule) usersGetUsername(l *lua.LState) int {
return 1
}

// @group users
// @summary Get user's friend status information for a list of target users.
// @param userID (type=string) The current user ID.
// @param userIDs(type=table) An array of target user IDs.
// @return friends(table) A list of user friends objects.
// @return error(error) An optional error value if an error occurred.
func (n *RuntimeLuaNakamaModule) usersGetFriendStatus(l *lua.LState) int {
id := l.CheckString(1)

uid, err := uuid.FromString(id)
if err != nil {
l.ArgError(1, "invalid user id")
}

ids := l.CheckTable(2)

uidsTable, ok := RuntimeLuaConvertLuaValue(ids).([]interface{})
if !ok {
l.ArgError(2, "invalid user ids list")
return 0
}

fids := make([]uuid.UUID, 0, len(uidsTable))
for _, id := range uidsTable {
ids, ok := id.(string)
if !ok || ids == "" {
l.ArgError(2, "each user id must be a string")
return 0
}
fid, err := uuid.FromString(ids)
if err != nil {
l.ArgError(2, "invalid user id")
return 0
}
fids = append(fids, fid)
}

friends, err := GetFriends(l.Context(), n.logger, n.db, n.statusRegistry, uid, fids)
if err != nil {
l.RaiseError("failed to get users friend status: %s", err.Error())
return 0
}

userFriends := l.CreateTable(len(friends), 0)
for i, f := range friends {
u := f.User

fut, err := userToLuaTable(l, u)
if err != nil {
l.RaiseError("failed to convert user data to lua table: %s", err.Error())
return 0
}

ft := l.CreateTable(0, 3)
ft.RawSetString("state", lua.LNumber(f.State.Value))
ft.RawSetString("update_time", lua.LNumber(f.UpdateTime.Seconds))
ft.RawSetString("user", fut)

userFriends.RawSetInt(i+1, ft)
}

l.Push(userFriends)
return 1
}

// @group users
// @summary Fetch one or more users randomly.
// @param count(type=int) The number of users to fetch.
Expand Down
Loading