From e0deb3f6c61bb87ff50882826a12b273cc653213 Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Mon, 21 Oct 2024 16:37:38 +0100 Subject: [PATCH 1/5] Add storage index pagination support --- CHANGELOG.md | 1 + server/api_test.go | 4 +- server/runtime_go_nakama.go | 10 +-- server/runtime_javascript_nakama.go | 45 ++++++---- server/runtime_lua_nakama.go | 13 ++- server/storage_index.go | 84 +++++++++++++++--- server/storage_index_test.go | 128 +++++++++++++++++++++++++--- 7 files changed, 238 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca8740642..c918c2d44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ### Changed - Increased limit on runtimes group users list functions. +- Added pagination support to storage index listing. ### Fixed - Ensure matchmaker stats behave correctly if matchmaker becomes fully empty and idle. diff --git a/server/api_test.go b/server/api_test.go index 89c9fc5b7..5e9d5cff9 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -171,8 +171,8 @@ func NewConsoleLogger(output *os.File, verbose bool) *zap.Logger { } func NewDB(t *testing.T) *sql.DB { - //dbUrl := "postgresql://postgres@127.0.0.1:5432/nakama?sslmode=disable" - dbUrl := "postgresql://root@127.0.0.1:26257/nakama?sslmode=disable" + dbUrl := "postgresql://postgres@127.0.0.1:5432/nakama?sslmode=disable" + // dbUrl := "postgresql://root@127.0.0.1:26257/nakama?sslmode=disable" if dbUrlEnv := os.Getenv("TEST_DB_URL"); len(dbUrlEnv) > 0 { dbUrl = dbUrlEnv } diff --git a/server/runtime_go_nakama.go b/server/runtime_go_nakama.go index 17a08e9de..5f46fc5db 100644 --- a/server/runtime_go_nakama.go +++ b/server/runtime_go_nakama.go @@ -2153,25 +2153,25 @@ func (n *RuntimeGoNakamaModule) StorageDelete(ctx context.Context, deletes []*ru // @param order(type=[]string, optional=true) The storage object fields to sort the query results by. The prefix '-' before a field name indicates descending order. All specified fields must be indexed and sortable. // @return objects(*api.StorageObjectList) A list of storage objects. // @return error(error) An optional error value if an error occurred. -func (n *RuntimeGoNakamaModule) StorageIndexList(ctx context.Context, callerID, indexName, query string, limit int, order []string) (*api.StorageObjects, error) { +func (n *RuntimeGoNakamaModule) StorageIndexList(ctx context.Context, callerID, indexName, query string, limit int, order []string, cursor string) (*api.StorageObjects, string, error) { cid := uuid.Nil if callerID != "" { id, err := uuid.FromString(callerID) if err != nil { - return nil, errors.New("expects caller id to be empty or a valid user id") + return nil, "", errors.New("expects caller id to be empty or a valid user id") } cid = id } if indexName == "" { - return nil, errors.New("expects a non-empty indexName") + return nil, "", errors.New("expects a non-empty indexName") } if limit < 1 || limit > 10_000 { - return nil, errors.New("limit must be 1-10000") + return nil, "", errors.New("limit must be 1-10000") } - return n.storageIndex.List(ctx, cid, indexName, query, limit, order) + return n.storageIndex.List(ctx, cid, indexName, query, limit, order, cursor) } // @group users diff --git a/server/runtime_javascript_nakama.go b/server/runtime_javascript_nakama.go index 537490f5f..3ae4b26e4 100644 --- a/server/runtime_javascript_nakama.go +++ b/server/runtime_javascript_nakama.go @@ -357,7 +357,7 @@ func (n *runtimeJavascriptNakamaModule) stringToBinary(r *goja.Runtime) func(goj // @param limit(type=int) Maximum number of results to be returned. // @param order(type=[]string, optional=true) The storage object fields to sort the query results by. The prefix '-' before a field name indicates descending order. All specified fields must be indexed and sortable. // @param callerId(type=string, optional=true) User ID of the caller, will apply permissions checks of the user. If empty defaults to system user and permission checks are bypassed. -// @return objects(nkruntime.StorageObjectList) A list of storage objects. +// @return objects(nkruntime.StorageIndexResult) A list of storage objects. // @return error(error) An optional error value if an error occurred. func (n *runtimeJavascriptNakamaModule) storageIndexList(r *goja.Runtime) func(goja.FunctionCall) goja.Value { return func(f goja.FunctionCall) goja.Value { @@ -391,26 +391,31 @@ func (n *runtimeJavascriptNakamaModule) storageIndexList(r *goja.Runtime) func(g callerID = cid } - objectList, err := n.storageIndex.List(n.ctx, callerID, idxName, queryString, int(limit), order) + var cursor string + if !goja.IsUndefined(f.Argument(5)) && !goja.IsNull(f.Argument(5)) { + cursor = getJsString(r, f.Argument(5)) + } + + objectList, newCursor, err := n.storageIndex.List(n.ctx, callerID, idxName, queryString, int(limit), order, cursor) if err != nil { panic(r.NewGoError(fmt.Errorf("failed to lookup storage index: %s", err.Error()))) } - objects := make([]interface{}, 0, len(objectList.Objects)) + objects := make([]any, 0, len(objectList.Objects)) for _, o := range objectList.Objects { - objectMap := make(map[string]interface{}, 9) - objectMap["key"] = o.Key - objectMap["collection"] = o.Collection + obj := r.NewObject() + obj.Set("key", o.Key) + obj.Set("collection", o.Collection) if o.UserId != "" { - objectMap["userId"] = o.UserId + obj.Set("userId", o.UserId) } else { - objectMap["userId"] = nil + obj.Set("userId", nil) } - objectMap["version"] = o.Version - objectMap["permissionRead"] = o.PermissionRead - objectMap["permissionWrite"] = o.PermissionWrite - objectMap["createTime"] = o.CreateTime.Seconds - objectMap["updateTime"] = o.UpdateTime.Seconds + obj.Set("version", o.Version) + obj.Set("permissionRead", o.PermissionRead) + obj.Set("permissionWrite", o.PermissionWrite) + obj.Set("createTime", o.CreateTime.Seconds) + obj.Set("updateTime", o.UpdateTime.Seconds) valueMap := make(map[string]interface{}) err = json.Unmarshal([]byte(o.Value), &valueMap) @@ -418,12 +423,20 @@ func (n *runtimeJavascriptNakamaModule) storageIndexList(r *goja.Runtime) func(g panic(r.NewGoError(fmt.Errorf("failed to convert value to json: %s", err.Error()))) } pointerizeSlices(valueMap) - objectMap["value"] = valueMap + obj.Set("value", valueMap) - objects = append(objects, objectMap) + objects = append(objects, obj) + } + + outObj := r.NewObject() + outObj.Set("objects", r.NewArray(objects...)) + if newCursor != "" { + outObj.Set("cursor", newCursor) + } else { + outObj.Set("cursor", goja.Null()) } - return r.ToValue(objects) + return r.ToValue(outObj) } } diff --git a/server/runtime_lua_nakama.go b/server/runtime_lua_nakama.go index c80c59533..62428e5db 100644 --- a/server/runtime_lua_nakama.go +++ b/server/runtime_lua_nakama.go @@ -10391,6 +10391,7 @@ func (n *RuntimeLuaNakamaModule) channelIdBuild(l *lua.LState) int { // @param order(type=[]string, optional=true) The storage object fields to sort the query results by. The prefix '-' before a field name indicates descending order. All specified fields must be indexed and sortable. // @param callerId(type=string, optional=true) User ID of the caller, will apply permissions checks of the user. If empty defaults to system user and permission checks are bypassed. // @return objects(table) A list of storage objects. +// @return objects(string) A cursor, if there's a next page of results, nil otherwise. // @return error(error) An optional error value if an error occurred. func (n *RuntimeLuaNakamaModule) storageIndexList(l *lua.LState) int { idxName := l.CheckString(1) @@ -10421,7 +10422,9 @@ func (n *RuntimeLuaNakamaModule) storageIndexList(l *lua.LState) int { callerID = cid } - objectList, err := n.storageIndex.List(l.Context(), callerID, idxName, queryString, limit, order) + cursor := l.OptString(6, "") + + objectList, newCursor, err := n.storageIndex.List(l.Context(), callerID, idxName, queryString, limit, order, cursor) if err != nil { l.RaiseError(err.Error()) return 0 @@ -10456,7 +10459,13 @@ func (n *RuntimeLuaNakamaModule) storageIndexList(l *lua.LState) int { } l.Push(lv) - return 1 + if newCursor != "" { + l.Push(lua.LString(newCursor)) + } else { + l.Push(lua.LNil) + } + + return 2 } // @group satori diff --git a/server/storage_index.go b/server/storage_index.go index 4ddcdae5d..7757de58f 100644 --- a/server/storage_index.go +++ b/server/storage_index.go @@ -15,11 +15,15 @@ package server import ( + "bytes" "context" "database/sql" + "encoding/base64" + "encoding/gob" "encoding/json" "errors" "fmt" + "slices" "time" "github.com/blugelabs/bluge" @@ -35,7 +39,7 @@ import ( type StorageIndex interface { Write(ctx context.Context, objects []*api.StorageObject) (creates int, deletes int) Delete(ctx context.Context, objects StorageOpDeletes) (deletes int) - List(ctx context.Context, callerID uuid.UUID, indexName, query string, limit int, order []string) (*api.StorageObjects, error) + List(ctx context.Context, callerID uuid.UUID, indexName, query string, limit int, order []string, cursor string) (*api.StorageObjects, string, error) Load(ctx context.Context) error CreateIndex(ctx context.Context, name, collection, key string, fields []string, sortFields []string, maxEntries int, indexOnly bool) error RegisterFilters(runtime *Runtime) @@ -218,10 +222,17 @@ func (si *LocalStorageIndex) Delete(ctx context.Context, objects StorageOpDelete return deletes } -func (si *LocalStorageIndex) List(ctx context.Context, callerID uuid.UUID, indexName, query string, limit int, order []string) (*api.StorageObjects, error) { +type indexListCursor struct { + Query string + Offset int + Limit int + Order []string +} + +func (si *LocalStorageIndex) List(ctx context.Context, callerID uuid.UUID, indexName, query string, limit int, order []string, cursor string) (*api.StorageObjects, string, error) { idx, found := si.indexByName[indexName] if !found { - return nil, fmt.Errorf("index %q not found", indexName) + return nil, "", fmt.Errorf("index %q not found", indexName) } if limit > idx.MaxEntries { @@ -232,34 +243,83 @@ func (si *LocalStorageIndex) List(ctx context.Context, callerID uuid.UUID, index query = "*" } + var idxCursor *indexListCursor + if cursor != "" { + idxCursor = &indexListCursor{} + cb, err := base64.RawURLEncoding.DecodeString(cursor) + if err != nil { + si.logger.Error("Could not base64 decode notification cursor.", zap.String("cursor", cursor)) + return nil, "", errors.New("invalid cursor") + } + if err := gob.NewDecoder(bytes.NewReader(cb)).Decode(idxCursor); err != nil { + si.logger.Error("Could not decode notification cursor.", zap.String("cursor", cursor)) + return nil, "", errors.New("invalid cursor") + } + + if query != idxCursor.Query { + return nil, "", fmt.Errorf("invalid cursor: query mismatch") + } + if limit != idxCursor.Limit { + return nil, "", fmt.Errorf("invalid cursor: limit mismatch") + } + if !slices.Equal(order, idxCursor.Order) { + return nil, "", fmt.Errorf("invalid cursor: order mismatch") + } + } + parsedQuery, err := ParseQueryString(query) if err != nil { - return nil, err + return nil, "", err } - searchReq := bluge.NewTopNSearch(limit, parsedQuery) + searchReq := bluge.NewTopNSearch(limit+1, parsedQuery) if len(order) != 0 { searchReq.SortBy(order) } + if idxCursor != nil { + searchReq.SetFrom(idxCursor.Offset) + } + indexReader, err := idx.Index.Reader() if err != nil { - return nil, err + return nil, "", err } results, err := indexReader.Search(ctx, searchReq) if err != nil { - return nil, err + return nil, "", err } indexResults, err := si.queryMatchesToStorageIndexResults(results) if err != nil { - return nil, err + return nil, "", err + } + + var newCursor string + if len(indexResults) > limit { + indexResults = indexResults[:len(indexResults)-1] + offset := 0 + if idxCursor != nil { + offset = idxCursor.Offset + } + newIdxCursor := &indexListCursor{ + Query: query, + Offset: offset + limit, + Limit: limit, + Order: order, + } + cursorBuf := new(bytes.Buffer) + if err := gob.NewEncoder(cursorBuf).Encode(newIdxCursor); err != nil { + si.logger.Error("Failed to create new cursor.", zap.Error(err)) + return nil, "", err + } + newCursor = base64.RawURLEncoding.EncodeToString(cursorBuf.Bytes()) } if len(indexResults) == 0 { - return &api.StorageObjects{Objects: []*api.StorageObject{}}, nil + return &api.StorageObjects{Objects: []*api.StorageObject{}}, "", nil } if !si.config.DisableIndexOnly && idx.IndexOnly { @@ -282,7 +342,7 @@ func (si *LocalStorageIndex) List(ctx context.Context, callerID uuid.UUID, index }) } - return &api.StorageObjects{Objects: objects}, nil + return &api.StorageObjects{Objects: objects}, newCursor, nil } storageReads := make([]*api.ReadStorageObjectId, 0, len(indexResults)) @@ -296,7 +356,7 @@ func (si *LocalStorageIndex) List(ctx context.Context, callerID uuid.UUID, index objects, err := StorageReadObjects(ctx, si.logger, si.db, callerID, storageReads) if err != nil { - return nil, err + return nil, "", err } // Sort the objects read from the db according to the results from the index as StorageReadObjects does not guarantee ordering of the results @@ -315,7 +375,7 @@ func (si *LocalStorageIndex) List(ctx context.Context, callerID uuid.UUID, index objects.Objects = sortedObjects - return objects, nil + return objects, newCursor, nil } func (si *LocalStorageIndex) Load(ctx context.Context) error { diff --git a/server/storage_index_test.go b/server/storage_index_test.go index 97b747f21..9184bca97 100644 --- a/server/storage_index_test.go +++ b/server/storage_index_test.go @@ -139,13 +139,13 @@ func TestLocalStorageIndex_Write(t *testing.T) { t.Fatal(err.Error()) } - entries, err := storageIdx.List(ctx, uuid.Nil, indexName1, "", maxEntries1, []string{}) // Match all + entries, _, err := storageIdx.List(ctx, uuid.Nil, indexName1, "", maxEntries1, []string{}, "") // Match all if err != nil { t.Fatal(err.Error()) } assert.Len(t, entries.Objects, 2, "indexed results length was not 2") - entries, err = storageIdx.List(ctx, uuid.Nil, indexName2, "", maxEntries1, []string{}) // Match all + entries, _, err = storageIdx.List(ctx, uuid.Nil, indexName2, "", maxEntries1, []string{}, "") // Match all if err != nil { t.Fatal(err.Error()) } @@ -186,7 +186,7 @@ func TestLocalStorageIndex_Write(t *testing.T) { t.Fatal(err.Error()) } - entries, err := storageIdx.List(ctx, uuid.Nil, indexName1, "+value.three:3", maxEntries1, []string{}) + entries, _, err := storageIdx.List(ctx, uuid.Nil, indexName1, "+value.three:3", maxEntries1, []string{}, "") if err != nil { t.Fatal(err.Error()) } @@ -289,7 +289,7 @@ func TestLocalStorageIndex_Write(t *testing.T) { t.Fatal(err.Error()) } - entries, err := storageIdx.List(ctx, uuid.Nil, indexName2, "", maxEntries2, []string{}) + entries, _, err := storageIdx.List(ctx, uuid.Nil, indexName2, "", maxEntries2, []string{}, "") if err != nil { t.Fatal(err.Error()) } @@ -316,6 +316,114 @@ func TestLocalStorageIndex_Write(t *testing.T) { } func TestLocalStorageIndex_List(t *testing.T) { + t.Run("paginates correctly", func(t *testing.T) { + db := NewDB(t) + defer db.Close() + + ctx := context.Background() + + nilUid := uuid.Nil + + u1 := uuid.Must(uuid.NewV4()) + InsertUser(t, db, u1) + + indexName := "test_index_only" + collection := "test_collection" + key := "key" + maxEntries := 10 + + valueOneBytes, _ := json.Marshal(map[string]any{ + "one": 1, + }) + valueOne := string(valueOneBytes) + valueTwoBytes, _ := json.Marshal(map[string]any{ + "two": 2, + }) + valueTwo := string(valueTwoBytes) + valueThreeBytes, _ := json.Marshal(map[string]any{ + "three": 3, + }) + valueThree := string(valueThreeBytes) + + storageIdx, err := NewLocalStorageIndex(logger, db, &StorageConfig{}, metrics) + if err != nil { + t.Fatal(err.Error()) + } + + if err := storageIdx.CreateIndex(ctx, indexName, collection, key, []string{"one", "two", "three"}, []string{}, maxEntries, true); err != nil { + t.Fatal(err.Error()) + } + + so1 := &StorageOpWrite{ + OwnerID: nilUid.String(), + Object: &api.WriteStorageObject{ + Collection: collection, + Key: key, + Value: valueOne, + }, + } + so2 := &StorageOpWrite{ + OwnerID: u1.String(), + Object: &api.WriteStorageObject{ + Collection: collection, + Key: key, + Value: valueTwo, + }, + } + so3 := &StorageOpWrite{ + OwnerID: u1.String(), + Object: &api.WriteStorageObject{ + Collection: collection, + Key: key, + Value: valueThree, + }, + } + + writeOps := StorageOpWrites{so1, so2, so3} + + if _, _, err := StorageWriteObjects(context.Background(), logger, db, metrics, storageIdx, true, writeOps); err != nil { + t.Fatal(err.Error()) + } + + // Page 1 + entries, cursor, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.two:2 value.three:3", 1, []string{}, "") + if err != nil { + t.Fatal(err.Error()) + } + assert.Len(t, entries.Objects, 1, "indexed results did not match query params") + assert.NotEmptyf(t, cursor, "cursor was empty when there's more results") + + // Page 2 + entries, cursor, err = storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.two:2 value.three:3", 1, []string{}, cursor) + if err != nil { + t.Fatal(err.Error()) + } + assert.Len(t, entries.Objects, 1, "indexed results did not match query params") + assert.NotEmptyf(t, cursor, "cursor was empty when there's more results") + + // Page 3 + entries, cursor, err = storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.two:2 value.three:3", 1, []string{}, cursor) + if err != nil { + t.Fatal(err.Error()) + } + assert.Len(t, entries.Objects, 1, "indexed results did not match query params") + assert.Empty(t, cursor, "cursor was not empty on last page") + + delOps := make(StorageOpDeletes, 0, len(writeOps)) + for _, op := range writeOps { + delOps = append(delOps, &StorageOpDelete{ + OwnerID: op.OwnerID, + ObjectID: &api.DeleteStorageObjectId{ + Collection: op.Object.Collection, + Key: op.Object.Key, + }, + }) + } + if _, err = StorageDeleteObjects(ctx, logger, db, storageIdx, true, delOps); err != nil { + t.Fatalf("Failed to teardown: %s", err.Error()) + } + }) + t.Run("when indexOnly is false, returns all matching results for query from the db", func(t *testing.T) { db := NewDB(t) defer db.Close() @@ -385,7 +493,7 @@ func TestLocalStorageIndex_List(t *testing.T) { t.Fatal(err.Error()) } - entries, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{}) + entries, _, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{}, "") if err != nil { t.Fatal(err.Error()) } @@ -481,7 +589,7 @@ func TestLocalStorageIndex_List(t *testing.T) { t.Fatal(err.Error()) } - entries, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{}) + entries, _, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{}, "") if err != nil { t.Fatal(err.Error()) } @@ -490,7 +598,7 @@ func TestLocalStorageIndex_List(t *testing.T) { assert.Equal(t, valueOne, entries.Objects[0].Value, "expected value retrieved from db did not match") assert.Equal(t, valueThree, entries.Objects[1].Value, "expected value retrieved from db did not match") - sortEntries, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{"value.sort"}) + sortEntries, _, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{"value.sort"}, "") if err != nil { t.Fatal(err.Error()) } @@ -498,7 +606,7 @@ func TestLocalStorageIndex_List(t *testing.T) { assert.Equal(t, valueOne, sortEntries.Objects[0].Value, "expected value retrieved from db did not match") assert.Equal(t, valueThree, sortEntries.Objects[1].Value, "expected value retrieved from db did not match") - sortDescEntries, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{"-value.sort"}) + sortDescEntries, _, err := storageIdx.List(ctx, uuid.Nil, indexName, "value.one:1 value.three:3", 10, []string{"-value.sort"}, "") if err != nil { t.Fatal(err.Error()) } @@ -573,7 +681,7 @@ func TestLocalStorageIndex_Delete(t *testing.T) { t.Fatal(err.Error()) } - entries, err := storageIdx.List(ctx, uuid.Nil, indexName, "", 10, []string{}) + entries, _, err := storageIdx.List(ctx, uuid.Nil, indexName, "", 10, []string{}, "") if err != nil { t.Fatal(err.Error()) } @@ -590,7 +698,7 @@ func TestLocalStorageIndex_Delete(t *testing.T) { t.Fatal(err.Error()) } - entries, err = storageIdx.List(ctx, uuid.Nil, indexName, "", 10, []string{}) + entries, _, err = storageIdx.List(ctx, uuid.Nil, indexName, "", 10, []string{}, "") if err != nil { t.Fatal(err.Error()) } From 25da4e218405f27327937615e79d7c421d3d865f Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Mon, 21 Oct 2024 16:38:43 +0100 Subject: [PATCH 2/5] Lint fixes --- server/runtime_javascript_nakama.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/runtime_javascript_nakama.go b/server/runtime_javascript_nakama.go index 3ae4b26e4..8b0f0a174 100644 --- a/server/runtime_javascript_nakama.go +++ b/server/runtime_javascript_nakama.go @@ -404,18 +404,18 @@ func (n *runtimeJavascriptNakamaModule) storageIndexList(r *goja.Runtime) func(g objects := make([]any, 0, len(objectList.Objects)) for _, o := range objectList.Objects { obj := r.NewObject() - obj.Set("key", o.Key) - obj.Set("collection", o.Collection) + _ = obj.Set("key", o.Key) + _ = obj.Set("collection", o.Collection) if o.UserId != "" { - obj.Set("userId", o.UserId) + _ = obj.Set("userId", o.UserId) } else { - obj.Set("userId", nil) + _ = obj.Set("userId", nil) } - obj.Set("version", o.Version) - obj.Set("permissionRead", o.PermissionRead) - obj.Set("permissionWrite", o.PermissionWrite) - obj.Set("createTime", o.CreateTime.Seconds) - obj.Set("updateTime", o.UpdateTime.Seconds) + _ = obj.Set("version", o.Version) + _ = obj.Set("permissionRead", o.PermissionRead) + _ = obj.Set("permissionWrite", o.PermissionWrite) + _ = obj.Set("createTime", o.CreateTime.Seconds) + _ = obj.Set("updateTime", o.UpdateTime.Seconds) valueMap := make(map[string]interface{}) err = json.Unmarshal([]byte(o.Value), &valueMap) @@ -423,17 +423,17 @@ func (n *runtimeJavascriptNakamaModule) storageIndexList(r *goja.Runtime) func(g panic(r.NewGoError(fmt.Errorf("failed to convert value to json: %s", err.Error()))) } pointerizeSlices(valueMap) - obj.Set("value", valueMap) + _ = obj.Set("value", valueMap) objects = append(objects, obj) } outObj := r.NewObject() - outObj.Set("objects", r.NewArray(objects...)) + _ = outObj.Set("objects", r.NewArray(objects...)) if newCursor != "" { - outObj.Set("cursor", newCursor) + _ = outObj.Set("cursor", newCursor) } else { - outObj.Set("cursor", goja.Null()) + _ = outObj.Set("cursor", goja.Null()) } return r.ToValue(outObj) From 54d6ace854bd2b97f842f19197a40bb591fd6248 Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Mon, 21 Oct 2024 16:43:22 +0100 Subject: [PATCH 3/5] vendor dependencies --- go.sum | 2 -- vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.sum b/go.sum index 7d24da4c5..feb8058ce 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,6 @@ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZH github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781 h1:mVjenNkPCNqQscldkvoBmmeCVS6MXzoN1rDd2u/+BbE= -github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781/go.mod h1:lPG64MVCs0/tEkh311Cd6oHX9NLx2vAPx7WW7QCJHQ0= github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a h1:tuL2ZPaeCbNw8rXmV9ywd00nXRv95V4/FmbIGKLQJAE= github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a/go.mod h1:hzCTPoEi/oml2BllVydJcNP63S7b56e5DzeQeLGvw1U= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go b/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go index 63f7a732d..c9664bb9c 100644 --- a/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go +++ b/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go @@ -1118,7 +1118,7 @@ type NakamaModule interface { StorageRead(ctx context.Context, reads []*StorageRead) ([]*api.StorageObject, error) StorageWrite(ctx context.Context, writes []*StorageWrite) ([]*api.StorageObjectAck, error) StorageDelete(ctx context.Context, deletes []*StorageDelete) error - StorageIndexList(ctx context.Context, callerID, indexName, query string, limit int, order []string) (*api.StorageObjects, error) + StorageIndexList(ctx context.Context, callerID, indexName, query string, limit int, order []string, cursor string) (*api.StorageObjects, string, error) MultiUpdate(ctx context.Context, accountUpdates []*AccountUpdate, storageWrites []*StorageWrite, storageDeletes []*StorageDelete, walletUpdates []*WalletUpdate, updateLedger bool) ([]*api.StorageObjectAck, []*WalletUpdateResult, error) From c7b3ea9a335bd49309607198ae6ab95dbbf1a46f Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Mon, 21 Oct 2024 18:21:10 +0100 Subject: [PATCH 4/5] Rebase --- go.mod | 4 +--- go.sum | 2 ++ vendor/modules.txt | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5bd935fcf..2098eb95a 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 - github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781 + github.com/heroiclabs/nakama-common v1.33.1-0.20241021170115-c7b5486ef0aa github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/jackc/pgx/v5 v5.6.0 @@ -74,5 +74,3 @@ require ( golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect ) - -replace github.com/heroiclabs/nakama-common => ../nakama-common diff --git a/go.sum b/go.sum index feb8058ce..26e97ee7d 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZH github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/heroiclabs/nakama-common v1.33.1-0.20241021170115-c7b5486ef0aa h1:AWkD2AwYSMexj1mPArQvs2xkZTkvXvzqdvHlN4UeHOs= +github.com/heroiclabs/nakama-common v1.33.1-0.20241021170115-c7b5486ef0aa/go.mod h1:lPG64MVCs0/tEkh311Cd6oHX9NLx2vAPx7WW7QCJHQ0= github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a h1:tuL2ZPaeCbNw8rXmV9ywd00nXRv95V4/FmbIGKLQJAE= github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a/go.mod h1:hzCTPoEi/oml2BllVydJcNP63S7b56e5DzeQeLGvw1U= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/vendor/modules.txt b/vendor/modules.txt index 1473c7c6f..36320bf63 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,7 +139,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/internal/genopena github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options github.com/grpc-ecosystem/grpc-gateway/v2/runtime github.com/grpc-ecosystem/grpc-gateway/v2/utilities -# github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781 => ../nakama-common +# github.com/heroiclabs/nakama-common v1.33.1-0.20241021170115-c7b5486ef0aa ## explicit; go 1.19 github.com/heroiclabs/nakama-common/api github.com/heroiclabs/nakama-common/rtapi @@ -417,4 +417,3 @@ gopkg.in/natefinch/lumberjack.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# github.com/heroiclabs/nakama-common => ../nakama-common From 3bcc41e1981a8b25e5648c698a8c6e2cf288a1ab Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Mon, 21 Oct 2024 18:25:56 +0100 Subject: [PATCH 5/5] Minor fixes --- server/api_test.go | 4 ++-- server/runtime_go_nakama.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/api_test.go b/server/api_test.go index 5e9d5cff9..89c9fc5b7 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -171,8 +171,8 @@ func NewConsoleLogger(output *os.File, verbose bool) *zap.Logger { } func NewDB(t *testing.T) *sql.DB { - dbUrl := "postgresql://postgres@127.0.0.1:5432/nakama?sslmode=disable" - // dbUrl := "postgresql://root@127.0.0.1:26257/nakama?sslmode=disable" + //dbUrl := "postgresql://postgres@127.0.0.1:5432/nakama?sslmode=disable" + dbUrl := "postgresql://root@127.0.0.1:26257/nakama?sslmode=disable" if dbUrlEnv := os.Getenv("TEST_DB_URL"); len(dbUrlEnv) > 0 { dbUrl = dbUrlEnv } diff --git a/server/runtime_go_nakama.go b/server/runtime_go_nakama.go index 5f46fc5db..8c6952eb4 100644 --- a/server/runtime_go_nakama.go +++ b/server/runtime_go_nakama.go @@ -2151,7 +2151,9 @@ func (n *RuntimeGoNakamaModule) StorageDelete(ctx context.Context, deletes []*ru // @param queryString(type=string) Query to filter index entries. // @param limit(type=int) Maximum number of results to be returned. // @param order(type=[]string, optional=true) The storage object fields to sort the query results by. The prefix '-' before a field name indicates descending order. All specified fields must be indexed and sortable. +// @param cursor(type=string) A cursor to fetch the next page of results. // @return objects(*api.StorageObjectList) A list of storage objects. +// @return cursor(type=string) An optional cursor that can be used to retrieve the next page of records (if any). // @return error(error) An optional error value if an error occurred. func (n *RuntimeGoNakamaModule) StorageIndexList(ctx context.Context, callerID, indexName, query string, limit int, order []string, cursor string) (*api.StorageObjects, string, error) { cid := uuid.Nil