Loading CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading runtime/runtime.go +1 −0 Original line number Diff line number Diff line Loading @@ -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) Loading server/console_account.go +2 −92 Original line number Diff line number Diff line Loading @@ -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.") return nil, err } 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 export, nil } Loading server/core_account.go +102 −0 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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 { Loading server/runtime.go +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
runtime/runtime.go +1 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
server/console_account.go +2 −92 Original line number Diff line number Diff line Loading @@ -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.") return nil, err } 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 export, nil } Loading
server/core_account.go +102 −0 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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 { Loading
server/runtime.go +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading