Skip to content

Commit

Permalink
Add new runtime function to get a list of user's friend status
Browse files Browse the repository at this point in the history
  • Loading branch information
sesposito committed Nov 12, 2024
1 parent c4a81a7 commit 0c85098
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
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

0 comments on commit 0c85098

Please sign in to comment.