From bc0a50fba8f136458a1b4ba00e08f90f4ce85e9c Mon Sep 17 00:00:00 2001 From: Andrei Mihu Date: Wed, 4 Sep 2019 18:05:26 +0100 Subject: [PATCH] New runtime function for full account data exports. --- CHANGELOG.md | 1 + runtime/runtime.go | 1 + server/console_account.go | 94 +--------------------------- server/core_account.go | 102 +++++++++++++++++++++++++++++++ server/runtime.go | 2 +- server/runtime_go.go | 5 +- server/runtime_go_nakama.go | 24 +++++++- server/runtime_lua.go | 12 ++-- server/runtime_lua_match_core.go | 6 +- server/runtime_lua_nakama.go | 28 ++++++++- tests/core_wallet_test.go | 14 ++--- 11 files changed, 176 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe364d43..95f5148d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - New `check` command to validate runtime modules without starting the server. - Add discrete channel identifier fields in all messages and message history listings. - Session tokens now allow storage of arbitrary string key-value pairs. +- New runtime function for full account data exports. ### Changed - Use Go 1.13.0 on Alpine 3.10 as base Docker container image and native builds. diff --git a/runtime/runtime.go b/runtime/runtime.go index 8ad616c6e..056a7cf9c 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -775,6 +775,7 @@ type NakamaModule interface { AccountUpdateId(ctx context.Context, userID, username string, metadata map[string]interface{}, displayName, timezone, location, langTag, avatarUrl string) error AccountDeleteId(ctx context.Context, userID string, recorded bool) error + AccountExportId(ctx context.Context, userID string) (string, error) UsersGetId(ctx context.Context, userIDs []string) ([]*api.User, error) UsersGetUsername(ctx context.Context, usernames []string) ([]*api.User, error) diff --git a/server/console_account.go b/server/console_account.go index 3b60ffa39..6135e553c 100644 --- a/server/console_account.go +++ b/server/console_account.go @@ -112,100 +112,10 @@ func (s *ConsoleServer) ExportAccount(ctx context.Context, in *console.AccountId return nil, status.Error(codes.InvalidArgument, "Cannot export the system user.") } - // Core user account. - account, _, err := GetAccount(ctx, s.logger, s.db, nil, userID) + export, err := ExportAccount(ctx, s.logger, s.db, userID) if err != nil { - if err == ErrAccountNotFound { - return nil, status.Error(codes.NotFound, "Account not found.") - } - s.logger.Error("Could not export account data", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - - // Friends. - friends, err := GetFriendIDs(ctx, s.logger, s.db, userID) - if err != nil { - s.logger.Error("Could not fetch friend IDs", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - - // Messages. - messages, err := GetChannelMessages(ctx, 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(ctx, s.logger, s.db, userID) - if err != nil { - s.logger.Error("Could not fetch leaderboard records", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - - groups := make([]*api.Group, 0) - groupUsers, err := ListUserGroups(ctx, s.logger, s.db, userID, 0, nil, "") - if err != nil { - s.logger.Error("Could not fetch groups that belong to the user", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - for _, g := range groupUsers.UserGroups { - groups = append(groups, g.Group) - } - - // Notifications. - notifications, err := NotificationList(ctx, s.logger, s.db, userID, 0, "", nil) - if err != nil { - s.logger.Error("Could not fetch notifications", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - - // Storage objects where user is the owner. - storageObjects, err := StorageReadAllUserObjects(ctx, s.logger, s.db, userID) - if err != nil { - s.logger.Error("Could not fetch notifications", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - - // History of user's wallet. - walletLedgers, err := ListWalletLedger(ctx, s.logger, s.db, userID) - if err != nil { - s.logger.Error("Could not fetch wallet ledger items", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - wl := make([]*console.WalletLedger, len(walletLedgers)) - for i, w := range walletLedgers { - changeset, err := json.Marshal(w.Changeset) - if err != nil { - s.logger.Error("Could not fetch wallet ledger items, error encoding changeset", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - metadata, err := json.Marshal(w.Metadata) - if err != nil { - s.logger.Error("Could not fetch wallet ledger items, error encoding metadata", zap.Error(err), zap.String("user_id", in.Id)) - return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") - } - wl[i] = &console.WalletLedger{ - Id: w.ID, - UserId: w.UserID, - Changeset: string(changeset), - Metadata: string(metadata), - CreateTime: ×tamp.Timestamp{Seconds: w.CreateTime}, - UpdateTime: ×tamp.Timestamp{Seconds: w.UpdateTime}, - } - } - - export := &console.AccountExport{ - Account: account, - Objects: storageObjects, - Friends: friends.GetFriends(), - Messages: messages, - Groups: groups, - LeaderboardRecords: leaderboardRecords, - Notifications: notifications.GetNotifications(), - WalletLedgers: wl, + return nil, err } - return export, nil } diff --git a/server/core_account.go b/server/core_account.go index 88730426d..7a83e4440 100644 --- a/server/core_account.go +++ b/server/core_account.go @@ -17,6 +17,10 @@ package server import ( "context" "database/sql" + "encoding/json" + "github.com/heroiclabs/nakama/console" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "strconv" "strings" "time" @@ -303,6 +307,104 @@ func UpdateAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID u return nil } +func ExportAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID uuid.UUID) (*console.AccountExport, error) { + // Core user account. + account, _, err := GetAccount(ctx, logger, db, nil, userID) + if err != nil { + if err == ErrAccountNotFound { + return nil, status.Error(codes.NotFound, "Account not found.") + } + logger.Error("Could not export account data", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + + // Friends. + friends, err := GetFriendIDs(ctx, logger, db, userID) + if err != nil { + logger.Error("Could not fetch friend IDs", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + + // Messages. + messages, err := GetChannelMessages(ctx, logger, db, userID) + if err != nil { + logger.Error("Could not fetch messages", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + + // Leaderboard records. + leaderboardRecords, err := LeaderboardRecordReadAll(ctx, logger, db, userID) + if err != nil { + logger.Error("Could not fetch leaderboard records", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + + groups := make([]*api.Group, 0) + groupUsers, err := ListUserGroups(ctx, logger, db, userID, 0, nil, "") + if err != nil { + logger.Error("Could not fetch groups that belong to the user", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + for _, g := range groupUsers.UserGroups { + groups = append(groups, g.Group) + } + + // Notifications. + notifications, err := NotificationList(ctx, logger, db, userID, 0, "", nil) + if err != nil { + logger.Error("Could not fetch notifications", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + + // Storage objects where user is the owner. + storageObjects, err := StorageReadAllUserObjects(ctx, logger, db, userID) + if err != nil { + logger.Error("Could not fetch notifications", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + + // History of user's wallet. + walletLedgers, err := ListWalletLedger(ctx, logger, db, userID) + if err != nil { + logger.Error("Could not fetch wallet ledger items", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + wl := make([]*console.WalletLedger, len(walletLedgers)) + for i, w := range walletLedgers { + changeset, err := json.Marshal(w.Changeset) + if err != nil { + logger.Error("Could not fetch wallet ledger items, error encoding changeset", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + metadata, err := json.Marshal(w.Metadata) + if err != nil { + logger.Error("Could not fetch wallet ledger items, error encoding metadata", zap.Error(err), zap.String("user_id", userID.String())) + return nil, status.Error(codes.Internal, "An error occurred while trying to export user data.") + } + wl[i] = &console.WalletLedger{ + Id: w.ID, + UserId: w.UserID, + Changeset: string(changeset), + Metadata: string(metadata), + CreateTime: ×tamp.Timestamp{Seconds: w.CreateTime}, + UpdateTime: ×tamp.Timestamp{Seconds: w.UpdateTime}, + } + } + + export := &console.AccountExport{ + Account: account, + Objects: storageObjects, + Friends: friends.GetFriends(), + Messages: messages, + Groups: groups, + LeaderboardRecords: leaderboardRecords, + Notifications: notifications.GetNotifications(), + WalletLedgers: wl, + } + + return export, nil +} + func DeleteAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID uuid.UUID, recorded bool) error { tx, err := db.BeginTx(ctx, nil) if err != nil { diff --git a/server/runtime.go b/server/runtime.go index 5b23bc08c..bbfbadf9c 100644 --- a/server/runtime.go +++ b/server/runtime.go @@ -439,7 +439,7 @@ func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler * eventQueue := NewRuntimeEventQueue(logger, config) startupLogger.Info("Runtime event queue processor started", zap.Int("size", config.GetRuntime().EventQueueSize), zap.Int("workers", config.GetRuntime().EventQueueWorkers)) - goModules, goRpcFunctions, goBeforeRtFunctions, goAfterRtFunctions, goBeforeReqFunctions, goAfterReqFunctions, goMatchmakerMatchedFunction, goMatchCreateFn, goTournamentEndFunction, goTournamentResetFunction, goLeaderboardResetFunction, allEventFunctions, goSetMatchCreateFn, goMatchNamesListFn, err := NewRuntimeProviderGo(logger, startupLogger, db, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, runtimeConfig.Path, paths, eventQueue) + goModules, goRpcFunctions, goBeforeRtFunctions, goAfterRtFunctions, goBeforeReqFunctions, goAfterReqFunctions, goMatchmakerMatchedFunction, goMatchCreateFn, goTournamentEndFunction, goTournamentResetFunction, goLeaderboardResetFunction, allEventFunctions, goSetMatchCreateFn, goMatchNamesListFn, err := NewRuntimeProviderGo(logger, startupLogger, db, jsonpbMarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, runtimeConfig.Path, paths, eventQueue) if err != nil { startupLogger.Error("Error initialising Go runtime provider", zap.Error(err)) return nil, err diff --git a/server/runtime_go.go b/server/runtime_go.go index 9084fb9c1..97ed65f3a 100644 --- a/server/runtime_go.go +++ b/server/runtime_go.go @@ -18,6 +18,7 @@ import ( "context" "database/sql" "errors" + "github.com/golang/protobuf/jsonpb" "path/filepath" "plugin" "strings" @@ -1750,10 +1751,10 @@ func (ri *RuntimeGoInitializer) RegisterMatch(name string, fn func(ctx context.C return nil } -func NewRuntimeProviderGo(logger, startupLogger *zap.Logger, db *sql.DB, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, rootPath string, paths []string, eventQueue *RuntimeEventQueue) ([]string, map[string]RuntimeRpcFunction, map[string]RuntimeBeforeRtFunction, map[string]RuntimeAfterRtFunction, *RuntimeBeforeReqFunctions, *RuntimeAfterReqFunctions, RuntimeMatchmakerMatchedFunction, RuntimeMatchCreateFunction, RuntimeTournamentEndFunction, RuntimeTournamentResetFunction, RuntimeLeaderboardResetFunction, *RuntimeEventFunctions, func(RuntimeMatchCreateFunction), func() []string, error) { +func NewRuntimeProviderGo(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, rootPath string, paths []string, eventQueue *RuntimeEventQueue) ([]string, map[string]RuntimeRpcFunction, map[string]RuntimeBeforeRtFunction, map[string]RuntimeAfterRtFunction, *RuntimeBeforeReqFunctions, *RuntimeAfterReqFunctions, RuntimeMatchmakerMatchedFunction, RuntimeMatchCreateFunction, RuntimeTournamentEndFunction, RuntimeTournamentResetFunction, RuntimeLeaderboardResetFunction, *RuntimeEventFunctions, func(RuntimeMatchCreateFunction), func() []string, error) { runtimeLogger := NewRuntimeGoLogger(logger) env := config.GetRuntime().Environment - nk := NewRuntimeGoNakamaModule(logger, db, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router) + nk := NewRuntimeGoNakamaModule(logger, db, jsonpbMarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router) match := make(map[string]func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) (runtime.Match, error), 0) matchLock := &sync.RWMutex{} diff --git a/server/runtime_go_nakama.go b/server/runtime_go_nakama.go index 1cc5c7c3d..f85a56b27 100644 --- a/server/runtime_go_nakama.go +++ b/server/runtime_go_nakama.go @@ -26,6 +26,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/ptypes/timestamp" "github.com/golang/protobuf/ptypes/wrappers" "github.com/heroiclabs/nakama/api" @@ -41,6 +42,7 @@ type RuntimeGoNakamaModule struct { sync.RWMutex logger *zap.Logger db *sql.DB + jsonpbMarshaler *jsonpb.Marshaler config Config socialClient *social.Client leaderboardCache LeaderboardCache @@ -57,10 +59,11 @@ type RuntimeGoNakamaModule struct { matchCreateFn RuntimeMatchCreateFunction } -func NewRuntimeGoNakamaModule(logger *zap.Logger, db *sql.DB, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter) *RuntimeGoNakamaModule { +func NewRuntimeGoNakamaModule(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter) *RuntimeGoNakamaModule { return &RuntimeGoNakamaModule{ logger: logger, db: db, + jsonpbMarshaler: jsonpbMarshaler, config: config, socialClient: socialClient, leaderboardCache: leaderboardCache, @@ -339,6 +342,25 @@ func (n *RuntimeGoNakamaModule) AccountDeleteId(ctx context.Context, userID stri return DeleteAccount(ctx, n.logger, n.db, u, recorded) } +func (n *RuntimeGoNakamaModule) AccountExportId(ctx context.Context, userID string) (string, error) { + u, err := uuid.FromString(userID) + if err != nil { + return "", errors.New("expects user ID to be a valid identifier") + } + + export, err := ExportAccount(ctx, n.logger, n.db, u) + if err != nil { + return "", errors.Errorf("error exporting account: %v", err.Error()) + } + + exportString, err := n.jsonpbMarshaler.MarshalToString(export) + if err != nil { + return "", errors.Errorf("error encoding account export: %v", err.Error()) + } + + return exportString, nil +} + func (n *RuntimeGoNakamaModule) UsersGetId(ctx context.Context, userIDs []string) ([]*api.User, error) { if len(userIDs) == 0 { return make([]*api.User, 0), nil diff --git a/server/runtime_lua.go b/server/runtime_lua.go index 69a5ca70f..119194a8d 100644 --- a/server/runtime_lua.go +++ b/server/runtime_lua.go @@ -138,7 +138,7 @@ func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpb if core != nil { return core, nil } - return NewRuntimeLuaMatchCore(logger, db, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, once, localCache, goMatchCreateFn, id, node, name) + return NewRuntimeLuaMatchCore(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, once, localCache, goMatchCreateFn, id, node, name) } runtimeProviderLua := &RuntimeProviderLua{ @@ -162,7 +162,7 @@ func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpb // Set the current count assuming we'll warm up the pool in a moment. currentCount: atomic.NewUint32(uint32(config.GetRuntime().MinCount)), newFn: func() *RuntimeLua { - r, err := newRuntimeLuaVM(logger, db, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, moduleCache, once, localCache, allMatchCreateFn, nil) + r, err := newRuntimeLuaVM(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, moduleCache, once, localCache, allMatchCreateFn, nil) if err != nil { logger.Fatal("Failed to initialize Lua runtime", zap.Error(err)) } @@ -172,7 +172,7 @@ func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpb statsCtx: context.Background(), } - r, err := newRuntimeLuaVM(logger, db, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, moduleCache, once, localCache, allMatchCreateFn, func(execMode RuntimeExecutionMode, id string) { + r, err := newRuntimeLuaVM(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, moduleCache, once, localCache, allMatchCreateFn, func(execMode RuntimeExecutionMode, id string) { switch execMode { case RuntimeExecutionModeRPC: rpcFunctions[id] = func(ctx context.Context, queryParams map[string][]string, userID, username string, vars map[string]string, expiry int64, sessionID, clientIP, clientPort, payload string) (string, error, codes.Code) { @@ -1796,7 +1796,7 @@ func checkRuntimeLuaVM(logger *zap.Logger, config Config, stdLibs map[string]lua vm.Push(lua.LString(name)) vm.Call(1, 0) } - nakamaModule := NewRuntimeLuaNakamaModule(nil, nil, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nakamaModule := NewRuntimeLuaNakamaModule(nil, nil, nil, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) vm.PreloadModule("nakama", nakamaModule.Loader) preload := vm.GetField(vm.GetField(vm.Get(lua.EnvironIndex), "package"), "preload") @@ -1818,7 +1818,7 @@ func checkRuntimeLuaVM(logger *zap.Logger, config Config, stdLibs map[string]lua return nil } -func newRuntimeLuaVM(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, stdLibs map[string]lua.LGFunction, moduleCache *RuntimeLuaModuleCache, once *sync.Once, localCache *RuntimeLuaLocalCache, matchCreateFn RuntimeMatchCreateFunction, announceCallbackFn func(RuntimeExecutionMode, string)) (*RuntimeLua, error) { +func newRuntimeLuaVM(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, stdLibs map[string]lua.LGFunction, moduleCache *RuntimeLuaModuleCache, once *sync.Once, localCache *RuntimeLuaLocalCache, matchCreateFn RuntimeMatchCreateFunction, announceCallbackFn func(RuntimeExecutionMode, string)) (*RuntimeLua, error) { vm := lua.NewState(lua.Options{ CallStackSize: config.GetRuntime().CallStackSize, RegistrySize: config.GetRuntime().RegistrySize, @@ -1854,7 +1854,7 @@ func newRuntimeLuaVM(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *jsonpb.U callbacks.LeaderboardReset = fn } } - nakamaModule := NewRuntimeLuaNakamaModule(logger, db, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, once, localCache, matchCreateFn, registerCallbackFn, announceCallbackFn) + nakamaModule := NewRuntimeLuaNakamaModule(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, once, localCache, matchCreateFn, registerCallbackFn, announceCallbackFn) vm.PreloadModule("nakama", nakamaModule.Loader) r := &RuntimeLua{ logger: logger, diff --git a/server/runtime_lua_match_core.go b/server/runtime_lua_match_core.go index b5164585f..14ce7b511 100644 --- a/server/runtime_lua_match_core.go +++ b/server/runtime_lua_match_core.go @@ -57,7 +57,7 @@ type RuntimeLuaMatchCore struct { ctxCancelFn context.CancelFunc } -func NewRuntimeLuaMatchCore(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, stdLibs map[string]lua.LGFunction, once *sync.Once, localCache *RuntimeLuaLocalCache, goMatchCreateFn RuntimeMatchCreateFunction, id uuid.UUID, node string, name string) (RuntimeMatchCore, error) { +func NewRuntimeLuaMatchCore(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, stdLibs map[string]lua.LGFunction, once *sync.Once, localCache *RuntimeLuaLocalCache, goMatchCreateFn RuntimeMatchCreateFunction, id uuid.UUID, node string, name string) (RuntimeMatchCore, error) { // Set up the Lua VM that will handle this match. vm := lua.NewState(lua.Options{ CallStackSize: config.GetRuntime().CallStackSize, @@ -81,10 +81,10 @@ func NewRuntimeLuaMatchCore(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *j if core != nil { return core, nil } - return NewRuntimeLuaMatchCore(logger, db, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, once, localCache, goMatchCreateFn, id, node, name) + return NewRuntimeLuaMatchCore(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, stdLibs, once, localCache, goMatchCreateFn, id, node, name) } - nakamaModule := NewRuntimeLuaNakamaModule(logger, db, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, once, localCache, allMatchCreateFn, nil, nil) + nakamaModule := NewRuntimeLuaNakamaModule(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, once, localCache, allMatchCreateFn, nil, nil) vm.PreloadModule("nakama", nakamaModule.Loader) // Create the context to be used throughout this match. diff --git a/server/runtime_lua_nakama.go b/server/runtime_lua_nakama.go index 22b40de98..e5d9c7a67 100644 --- a/server/runtime_lua_nakama.go +++ b/server/runtime_lua_nakama.go @@ -57,6 +57,7 @@ import ( type RuntimeLuaNakamaModule struct { logger *zap.Logger db *sql.DB + jsonpbMarshaler *jsonpb.Marshaler jsonpbUnmarshaler *jsonpb.Unmarshaler config Config socialClient *social.Client @@ -78,10 +79,11 @@ type RuntimeLuaNakamaModule struct { matchCreateFn RuntimeMatchCreateFunction } -func NewRuntimeLuaNakamaModule(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, once *sync.Once, localCache *RuntimeLuaLocalCache, matchCreateFn RuntimeMatchCreateFunction, registerCallbackFn func(RuntimeExecutionMode, string, *lua.LFunction), announceCallbackFn func(RuntimeExecutionMode, string)) *RuntimeLuaNakamaModule { +func NewRuntimeLuaNakamaModule(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, once *sync.Once, localCache *RuntimeLuaLocalCache, matchCreateFn RuntimeMatchCreateFunction, registerCallbackFn func(RuntimeExecutionMode, string, *lua.LFunction), announceCallbackFn func(RuntimeExecutionMode, string)) *RuntimeLuaNakamaModule { return &RuntimeLuaNakamaModule{ logger: logger, db: db, + jsonpbMarshaler: jsonpbMarshaler, jsonpbUnmarshaler: jsonpbUnmarshaler, config: config, socialClient: socialClient, @@ -164,6 +166,7 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "accounts_get_id": n.accountsGetId, "account_update_id": n.accountUpdateId, "account_delete_id": n.accountDeleteId, + "account_export_id": n.accountExportId, "users_get_id": n.usersGetId, "users_get_username": n.usersGetUsername, "users_ban_id": n.usersBanId, @@ -5351,3 +5354,26 @@ func (n *RuntimeLuaNakamaModule) accountDeleteId(l *lua.LState) int { return 0 } + +func (n *RuntimeLuaNakamaModule) accountExportId(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 + } + + export, err := ExportAccount(l.Context(), n.logger, n.db, userID) + if err != nil { + l.RaiseError("error exporting account: %v", err.Error()) + return 0 + } + + exportString, err := n.jsonpbMarshaler.MarshalToString(export) + if err != nil { + l.RaiseError("error encoding account export: %v", err.Error()) + return 0 + } + + l.Push(lua.LString(exportString)) + return 1 +} diff --git a/tests/core_wallet_test.go b/tests/core_wallet_test.go index 5a600d334..06609aa92 100644 --- a/tests/core_wallet_test.go +++ b/tests/core_wallet_test.go @@ -57,7 +57,7 @@ func TestUpdateWalletSingleUser(t *testing.T) { } db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) userID, _, _, err := server.AuthenticateCustom(context.Background(), logger, db, uuid.Must(uuid.NewV4()).String(), uuid.Must(uuid.NewV4()).String(), true) if err != nil { @@ -135,7 +135,7 @@ func TestUpdateWalletMultiUser(t *testing.T) { } db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) count := 5 userIDs := make([]string, 0, count) @@ -222,7 +222,7 @@ func TestUpdateWalletsMultiUser(t *testing.T) { } db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) count := 5 userIDs := make([]string, 0, count) @@ -314,7 +314,7 @@ func TestUpdateWalletsMultiUserSharedChangeset(t *testing.T) { } db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) count := 5 userIDs := make([]string, 0, count) @@ -410,7 +410,7 @@ func TestUpdateWalletsMultiUserSharedChangesetDeductions(t *testing.T) { } db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) count := 5 userIDs := make([]string, 0, count) @@ -465,7 +465,7 @@ func TestUpdateWalletsMultiUserSharedChangesetDeductions(t *testing.T) { func TestUpdateWalletsSingleUser(t *testing.T) { db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) userID, _, _, err := server.AuthenticateCustom(context.Background(), logger, db, uuid.Must(uuid.NewV4()).String(), uuid.Must(uuid.NewV4()).String(), true) if err != nil { @@ -512,7 +512,7 @@ func TestUpdateWalletsSingleUser(t *testing.T) { func TestUpdateWalletRepeatedSingleUser(t *testing.T) { db := NewDB(t) - nk := server.NewRuntimeGoNakamaModule(logger, db, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) + nk := server.NewRuntimeGoNakamaModule(logger, db, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil) userID, _, _, err := server.AuthenticateCustom(context.Background(), logger, db, uuid.Must(uuid.NewV4()).String(), uuid.Must(uuid.NewV4()).String(), true) if err != nil { -- GitLab