From d87cd1bd0bcf3dba6664736e0e84d3eba84c0764 Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Thu, 7 Nov 2024 17:18:21 +0000 Subject: [PATCH 1/2] Add new Follow/Unfollow runtime APIs Add Follow/Unfollow runtime function. Add NotificationsUpodate runtime function. --- CHANGELOG.md | 4 + go.mod | 2 + go.sum | 2 - server/core_notification.go | 36 ++++ server/runtime_go_nakama.go | 83 ++++++++ server/runtime_javascript_nakama.go | 145 +++++++++++++ server/runtime_lua_nakama.go | 191 ++++++++++++++++++ .../nakama-common/runtime/runtime.go | 11 + vendor/modules.txt | 3 +- 9 files changed, 474 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bce926fdf..4e7c8cf0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ 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 Follow/Unfollow runtime APIs. +- Add new NotificationsUpdate runtime API. + ### Changed - Increase limit of runtime friend listing operations to 1,000. diff --git a/go.mod b/go.mod index 4263197bb..57f785c49 100644 --- a/go.mod +++ b/go.mod @@ -74,3 +74,5 @@ 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 9e96678ee..841b5b1ba 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.34.0 h1:7/F5v5yoCFBMTn5Aih/cqR/GW7hbEbup8blq5OmhzjM= -github.com/heroiclabs/nakama-common v1.34.0/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/server/core_notification.go b/server/core_notification.go index bb67f78e7..0eeeb260a 100644 --- a/server/core_notification.go +++ b/server/core_notification.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "github.com/heroiclabs/nakama-common/runtime" + "github.com/jackc/pgx/v5" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "time" @@ -436,3 +437,38 @@ func NotificationsDeleteId(ctx context.Context, logger *zap.Logger, db *sql.DB, return nil } + +type notificationUpdate struct { + Id uuid.UUID + Content map[string]any + Subject *string + Sender *string +} + +func NotificationsUpdate(ctx context.Context, logger *zap.Logger, db *sql.DB, updates ...notificationUpdate) error { + if len(updates) == 0 { + // NOOP + return nil + } + + b := &pgx.Batch{} + for _, update := range updates { + b.Queue("UPDATE notification SET content = coalesce($1, content), subject = coalesce($2, subject), sender_id = coalesce($3, sender_id) WHERE id = $4", update.Content, update.Subject, update.Sender, update.Id) + } + + if err := ExecuteInTxPgx(ctx, db, func(tx pgx.Tx) error { + r := tx.SendBatch(ctx, b) + _, err := r.Exec() + defer r.Close() + if err != nil { + return err + } + + return nil + }); err != nil { + logger.Error("failed to update notifications", zap.Error(err)) + return fmt.Errorf("failed to update notifications: %s", err.Error()) + } + + return nil +} diff --git a/server/runtime_go_nakama.go b/server/runtime_go_nakama.go index 92da058c1..6613da04e 100644 --- a/server/runtime_go_nakama.go +++ b/server/runtime_go_nakama.go @@ -1820,6 +1820,31 @@ func (n *RuntimeGoNakamaModule) NotificationsDeleteId(ctx context.Context, userI return NotificationsDeleteId(ctx, n.logger, n.db, userID, ids...) } +// @group notifications +// @summary Update notifications by their id. +// @param ctx(type=context.Context) The context object represents information about the server and requester. +// @param userID(type=[]runtime.NotificationUpdate) +// @return error(error) An optional error value if an error occurred. +func (n *RuntimeGoNakamaModule) NotificationsUpdate(ctx context.Context, updates ...runtime.NotificationUpdate) error { + nUpdates := make([]notificationUpdate, 0, len(updates)) + + for _, update := range updates { + uid, err := uuid.FromString(update.Id) + if err != nil { + return errors.New("expects id to be a valid UUID") + } + + nUpdates = append(nUpdates, notificationUpdate{ + Id: uid, + Content: update.Content, + Subject: update.Subject, + Sender: update.Sender, + }) + } + + return NotificationsUpdate(ctx, n.logger, n.db, nUpdates...) +} + // @group wallets // @summary Update a user's wallet with the given changeset. // @param ctx(type=context.Context) The context object represents information about the server and requester. @@ -1954,6 +1979,64 @@ func (n *RuntimeGoNakamaModule) WalletLedgerList(ctx context.Context, userID str return runtimeItems, newCursor, nil } +// @group status +// @summary Follow a player's status changes on a given session. +// @param sessionID(type=string) A valid session identifier. +// @param userIDs(type=[]string) A list of userIDs to follow. +// @return error(error) An optional error value if an error occurred. +func (n *RuntimeGoNakamaModule) StatusFollow(sessionID string, userIDs []string) error { + suid, err := uuid.FromString(sessionID) + if err != nil { + return errors.New("expects a valid session id") + } + + if len(userIDs) == 0 { + return nil + } + + uids := make(map[uuid.UUID]struct{}, len(userIDs)) + for _, id := range userIDs { + uid, err := uuid.FromString(id) + if err != nil { + return errors.New("expects a valid user id") + } + uids[uid] = struct{}{} + } + + n.statusRegistry.Follow(suid, uids) + + return nil +} + +// @group status +// @summary Unfollow a player's status changes on a given session. +// @param sessionID(type=string) A valid session identifier. +// @param userIDs(type=[]string) A list of userIDs to unfollow. +// @return error(error) An optional error value if an error occurred. +func (n *RuntimeGoNakamaModule) StatusUnfollow(sessionID string, userIDs []string) error { + suid, err := uuid.FromString(sessionID) + if err != nil { + return errors.New("expects a valid session id") + } + + if len(userIDs) == 0 { + return nil + } + + uids := make([]uuid.UUID, 0, len(userIDs)) + for _, id := range userIDs { + uid, err := uuid.FromString(id) + if err != nil { + return errors.New("expects a valid user id") + } + uids = append(uids, uid) + } + + n.statusRegistry.Unfollow(suid, uids) + + return nil +} + // @group storage // @summary List records in a collection and page through results. The records returned can be filtered to those owned by the user or "" for public records. // @param ctx(type=context.Context) The context object represents information about the server and requester. diff --git a/server/runtime_javascript_nakama.go b/server/runtime_javascript_nakama.go index e56f352b0..544def407 100644 --- a/server/runtime_javascript_nakama.go +++ b/server/runtime_javascript_nakama.go @@ -228,12 +228,15 @@ func (n *runtimeJavascriptNakamaModule) mappings(r *goja.Runtime) map[string]fun "notificationsList": n.notificationsList(r), "notificationsSend": n.notificationsSend(r), "notificationsDelete": n.notificationsDelete(r), + "notificationsUpdate": n.notificationsUpdate(r), "notificationsGetId": n.notificationsGetId(r), "notificationsDeleteId": n.notificationsDeleteId(r), "walletUpdate": n.walletUpdate(r), "walletsUpdate": n.walletsUpdate(r), "walletLedgerUpdate": n.walletLedgerUpdate(r), "walletLedgerList": n.walletLedgerList(r), + "statusFollow": n.statusFollow(r), + "statusUnfollow": n.statusUnfollow(r), "storageList": n.storageList(r), "storageRead": n.storageRead(r), "storageWrite": n.storageWrite(r), @@ -4017,6 +4020,74 @@ func (n *runtimeJavascriptNakamaModule) notificationsDelete(r *goja.Runtime) fun } } +// @group notifications +// @summary Update notifications by their id. +// @param updates(type=nkruntime.NotificationUpdate[]) +// @return error(error) An optional error value if an error occurred. +func (n *runtimeJavascriptNakamaModule) notificationsUpdate(r *goja.Runtime) func(goja.FunctionCall) goja.Value { + return func(f goja.FunctionCall) goja.Value { + updatesIn := f.Argument(0) + + dataSlice, err := exportToSlice[[]map[string]any](updatesIn) + if err != nil { + panic(r.NewTypeError("expects an array of notification updates objects")) + } + + nUpdates := make([]notificationUpdate, 0, len(dataSlice)) + for _, u := range dataSlice { + update := notificationUpdate{} + id, ok := u["id"] + if !ok || id == "" { + panic(r.NewTypeError("expects 'id' value to be set")) + } + idstr, ok := id.(string) + if !ok || idstr == "" { + panic(r.NewTypeError("expects 'id' value to be a non-empty string")) + } + uid, err := uuid.FromString(idstr) + if err != nil { + panic(r.NewGoError(fmt.Errorf("expects 'id' value to be a valid id"))) + } + update.Id = uid + + content, ok := u["content"] + if ok { + cmap, ok := content.(map[string]any) + if !ok { + panic(r.NewTypeError("expects 'content' value to be a non-empty map")) + } + update.Content = cmap + } + + subject, ok := u["subject"] + if ok { + substr, ok := subject.(string) + if !ok || substr == "" { + panic(r.NewTypeError("expects 'subject' value to be a non-empty string")) + } + update.Subject = &substr + } + + sender, ok := u["sender"] + if ok { + substr, ok := sender.(string) + if !ok || substr == "" { + panic(r.NewTypeError("expects 'sender' value to be a non-empty string")) + } + update.Sender = &substr + } + + nUpdates = append(nUpdates, update) + } + + if err := NotificationsUpdate(n.ctx, n.logger, n.db, nUpdates...); err != nil { + panic(r.NewGoError(fmt.Errorf("failed to update notifications: %s", err.Error()))) + } + + return goja.Undefined() + } +} + // @group notifications // @summary Get notifications by their id. // @param ids(type=string[]) A list of notification ids. @@ -4322,6 +4393,80 @@ func (n *runtimeJavascriptNakamaModule) walletLedgerUpdate(r *goja.Runtime) func } } +// @group status +// @summary Follow a player's status changes on a given session. +// @param sessionID(type=string) A valid session identifier. +// @param userIDs(type=string[]) A list of userIDs to follow. +// @return error(error) An optional error value if an error occurred. +func (n *runtimeJavascriptNakamaModule) statusFollow(r *goja.Runtime) func(goja.FunctionCall) goja.Value { + return func(f goja.FunctionCall) goja.Value { + sid := getJsString(r, f.Argument(0)) + + suid, err := uuid.FromString(sid) + if err != nil { + panic(r.NewTypeError("expects a valid session id")) + } + + uidsIn := f.Argument(1) + + uidsSlice, err := exportToSlice[[]string](uidsIn) + + if len(uidsSlice) == 0 { + return goja.Undefined() + } + + uids := make(map[uuid.UUID]struct{}, len(uidsSlice)) + for _, id := range uidsSlice { + uid, err := uuid.FromString(id) + if err != nil { + panic(r.NewTypeError("expects a valid user id")) + } + uids[uid] = struct{}{} + } + + n.statusRegistry.Follow(suid, uids) + + return nil + } +} + +// @group status +// @summary Unfollow a player's status changes on a given session. +// @param sessionID(type=string) A valid session identifier. +// @param userIDs(type=string[]) A list of userIDs to unfollow. +// @return error(error) An optional error value if an error occurred. +func (n *runtimeJavascriptNakamaModule) statusUnfollow(r *goja.Runtime) func(goja.FunctionCall) goja.Value { + return func(f goja.FunctionCall) goja.Value { + sid := getJsString(r, f.Argument(0)) + + suid, err := uuid.FromString(sid) + if err != nil { + panic(r.NewTypeError("expects a valid session id")) + } + + uidsIn := f.Argument(1) + + uidsSlice, err := exportToSlice[[]string](uidsIn) + + if len(uidsSlice) == 0 { + return goja.Undefined() + } + + uids := make([]uuid.UUID, 0, len(uidsSlice)) + for _, id := range uidsSlice { + uid, err := uuid.FromString(id) + if err != nil { + panic(r.NewTypeError("expects a valid user id")) + } + uids = append(uids, uid) + } + + n.statusRegistry.Unfollow(suid, uids) + + return nil + } +} + // @group wallets // @summary List all wallet updates for a particular user from oldest to newest. // @param userId(type=string) The ID of the user to list wallet updates for. diff --git a/server/runtime_lua_nakama.go b/server/runtime_lua_nakama.go index 210a91eb0..bf4a23c78 100644 --- a/server/runtime_lua_nakama.go +++ b/server/runtime_lua_nakama.go @@ -247,10 +247,13 @@ func (n *RuntimeLuaNakamaModule) Loader(l *lua.LState) int { "notifications_delete": n.notificationsDelete, "notifications_get_id": n.notificationsGetId, "notifications_delete_id": n.notificationsDeleteId, + "notifications_update": n.notificationsUpdate, "wallet_update": n.walletUpdate, "wallets_update": n.walletsUpdate, "wallet_ledger_update": n.walletLedgerUpdate, "wallet_ledger_list": n.walletLedgerList, + "status_follow": n.statusFollow, + "status_unfollow": n.statusUnfollow, "storage_list": n.storageList, "storage_read": n.storageRead, "storage_write": n.storageWrite, @@ -5357,6 +5360,110 @@ func (n *RuntimeLuaNakamaModule) notificationsDelete(l *lua.LState) int { return 0 } +// @group notifications +// @summary Update notifications by their id. +// @param updates(type=table) A list of notifications to be updated. +// @return error(error) An optional error value if an error occurred. +func (n *RuntimeLuaNakamaModule) notificationsUpdate(l *lua.LState) int { + updatesIn := l.CheckTable(1) + + updates, err := tableToNotificationUpdates(l, updatesIn) + if err != nil { + return 0 + } + + if err := NotificationsUpdate(l.Context(), n.logger, n.db, updates...); err != nil { + l.RaiseError(fmt.Sprintf("failed to update notifications: %s", err.Error())) + } + + return 0 +} + +func tableToNotificationUpdates(l *lua.LState, dataTable *lua.LTable) ([]notificationUpdate, error) { + size := dataTable.Len() + updates := make([]notificationUpdate, 0, size) + conversionError := false + + dataTable.ForEach(func(k, v lua.LValue) { + if conversionError { + return + } + + dataTable, ok := v.(*lua.LTable) + if !ok { + conversionError = true + l.ArgError(1, "expects a valid set of data") + return + } + + update := notificationUpdate{} + dataTable.ForEach(func(k, v lua.LValue) { + if conversionError { + return + } + + switch k.String() { + case "id": + if v.Type() != lua.LTString { + conversionError = true + l.ArgError(1, "expects id to be string") + return + } + var uid uuid.UUID + var err error + if uid, err = uuid.FromString(v.String()); err != nil { + conversionError = true + l.ArgError(1, "expects user_id to be a valid ID") + return + } + update.Id = uid + case "content": + if v.Type() != lua.LTTable { + conversionError = true + l.ArgError(1, "expects content to be table") + return + } + valueMap := RuntimeLuaConvertLuaTable(v.(*lua.LTable)) + update.Content = valueMap + case "subject": + if v.Type() != lua.LTString { + conversionError = true + l.ArgError(1, "expects subject to be string") + return + } + if v.String() == "" { + conversionError = true + l.ArgError(1, "expects subject to be a non-empty string") + return + } + s := v.String() + update.Subject = &s + case "sender": + if v.Type() != lua.LTString { + conversionError = true + l.ArgError(1, "expects sender to be string") + return + } + if v.String() == "" { + conversionError = true + l.ArgError(1, "expects sender to be a non-empty string") + return + } + s := v.String() + update.Sender = &s + } + }) + + if conversionError { + return + } + + updates = append(updates, update) + }) + + return updates, nil +} + // @group notifications // @summary Get notifications by their id. // @param ids(type=table) A list of notification ids. @@ -5770,6 +5877,90 @@ func (n *RuntimeLuaNakamaModule) walletLedgerList(l *lua.LState) int { return 2 } +// @group status +// @summary Follow a player's status changes on a given session. +// @param sessionID(type=string) A valid session identifier. +// @param userIDs(type=table) A list of userIDs to follow. +// @return error(error) An optional error value if an error occurred. +func (n *RuntimeLuaNakamaModule) statusFollow(l *lua.LState) int { + sid := l.CheckString(1) + + suid, err := uuid.FromString(sid) + if err != nil { + l.ArgError(1, "expects a valid session id") + return 0 + } + + uidsIn := l.CheckTable(2) + + uidsTable, ok := RuntimeLuaConvertLuaValue(uidsIn).([]interface{}) + if !ok { + l.ArgError(2, "invalid user ids list") + return 0 + } + + uids := make(map[uuid.UUID]struct{}, len(uidsTable)) + for _, id := range uidsTable { + ids, ok := id.(string) + if !ok || ids == "" { + l.ArgError(1, "each user id must be a string") + return 0 + } + uid, err := uuid.FromString(ids) + if err != nil { + l.ArgError(1, "each user id must be a valid id") + return 0 + } + uids[uid] = struct{}{} + } + + n.statusRegistry.Follow(suid, uids) + + return 0 +} + +// @group status +// @summary Unfollow a player's status changes on a given session. +// @param sessionID(type=string) A valid session identifier. +// @param userIDs(type=table) A list of userIDs to unfollow. +// @return error(error) An optional error value if an error occurred. +func (n *RuntimeLuaNakamaModule) statusUnfollow(l *lua.LState) int { + sid := l.CheckString(1) + + suid, err := uuid.FromString(sid) + if err != nil { + l.ArgError(1, "expects a valid session id") + return 0 + } + + uidsIn := l.CheckTable(2) + + uidsTable, ok := RuntimeLuaConvertLuaValue(uidsIn).([]interface{}) + if !ok { + l.ArgError(2, "invalid user ids list") + return 0 + } + + uids := make([]uuid.UUID, 0, len(uidsTable)) + for _, id := range uidsTable { + ids, ok := id.(string) + if !ok || ids == "" { + l.ArgError(1, "each user id must be a string") + return 0 + } + uid, err := uuid.FromString(ids) + if err != nil { + l.ArgError(1, "each user id must be a valid id") + return 0 + } + uids = append(uids, uid) + } + + n.statusRegistry.Unfollow(suid, uids) + + return 0 +} + // @group storage // @summary List records in a collection and page through results. The records returned can be filtered to those owned by the user or "" for public records. // @param userId(type=string) User ID to list records for or "" (empty string) | void for public records. diff --git a/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go b/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go index 90a269f26..4669736bf 100644 --- a/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go +++ b/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go @@ -973,6 +973,13 @@ type Notification struct { Persistent bool } +type NotificationUpdate struct { + Id string + Subject *string + Content map[string]any + Sender *string +} + type WalletUpdate struct { UserID string Changeset map[string]int64 @@ -1109,6 +1116,7 @@ type NakamaModule interface { NotificationsList(ctx context.Context, userID string, limit int, cursor string) ([]*api.Notification, string, error) NotificationsSend(ctx context.Context, notifications []*NotificationSend) error NotificationSendAll(ctx context.Context, subject string, content map[string]interface{}, code int, persistent bool) error + NotificationsUpdate(ctx context.Context, updates ...NotificationUpdate) error NotificationsDelete(ctx context.Context, notifications []*NotificationDelete) error NotificationsGetId(ctx context.Context, userID string, ids []string) ([]*Notification, error) NotificationsDeleteId(ctx context.Context, userID string, ids []string) error @@ -1201,6 +1209,9 @@ type NakamaModule interface { ChannelMessageRemove(ctx context.Context, channelId, messageId string, senderId, senderUsername string, persist bool) (*rtapi.ChannelMessageAck, error) ChannelMessagesList(ctx context.Context, channelId string, limit int, forward bool, cursor string) (messages []*api.ChannelMessage, nextCursor string, prevCursor string, err error) + StatusFollow(sessionID string, userIDs []string) error + StatusUnfollow(sessionID string, userIDs []string) error + GetSatori() Satori GetFleetManager() FleetManager } diff --git a/vendor/modules.txt b/vendor/modules.txt index a6256d1a8..fb268a944 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.34.0 +# github.com/heroiclabs/nakama-common v1.34.0 => ../nakama-common ## explicit; go 1.19 github.com/heroiclabs/nakama-common/api github.com/heroiclabs/nakama-common/rtapi @@ -417,3 +417,4 @@ 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 5b39d5c80740422a9c279018b0975701cb099af3 Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Thu, 7 Nov 2024 17:36:38 +0000 Subject: [PATCH 2/2] Lint fixes --- server/runtime_javascript_nakama.go | 6 ++++++ server/runtime_lua_nakama.go | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/server/runtime_javascript_nakama.go b/server/runtime_javascript_nakama.go index 544def407..090c93094 100644 --- a/server/runtime_javascript_nakama.go +++ b/server/runtime_javascript_nakama.go @@ -4410,6 +4410,9 @@ func (n *runtimeJavascriptNakamaModule) statusFollow(r *goja.Runtime) func(goja. uidsIn := f.Argument(1) uidsSlice, err := exportToSlice[[]string](uidsIn) + if err != nil { + panic(r.NewTypeError("expects an array of user ids")) + } if len(uidsSlice) == 0 { return goja.Undefined() @@ -4447,6 +4450,9 @@ func (n *runtimeJavascriptNakamaModule) statusUnfollow(r *goja.Runtime) func(goj uidsIn := f.Argument(1) uidsSlice, err := exportToSlice[[]string](uidsIn) + if err != nil { + panic(r.NewTypeError("expects an array of user ids")) + } if len(uidsSlice) == 0 { return goja.Undefined() diff --git a/server/runtime_lua_nakama.go b/server/runtime_lua_nakama.go index bf4a23c78..3375c3184 100644 --- a/server/runtime_lua_nakama.go +++ b/server/runtime_lua_nakama.go @@ -5353,7 +5353,7 @@ func (n *RuntimeLuaNakamaModule) notificationsDelete(l *lua.LState) int { for uid, notificationIDs := range notifications { if err := NotificationDelete(l.Context(), n.logger, n.db, uid, notificationIDs); err != nil { - l.RaiseError(fmt.Sprintf("failed to delete notifications: %s", err.Error())) + l.RaiseError("failed to delete notifications: %s", err.Error()) } } @@ -5373,7 +5373,7 @@ func (n *RuntimeLuaNakamaModule) notificationsUpdate(l *lua.LState) int { } if err := NotificationsUpdate(l.Context(), n.logger, n.db, updates...); err != nil { - l.RaiseError(fmt.Sprintf("failed to update notifications: %s", err.Error())) + l.RaiseError("failed to update notifications: %s", err.Error()) } return 0 @@ -5903,12 +5903,12 @@ func (n *RuntimeLuaNakamaModule) statusFollow(l *lua.LState) int { for _, id := range uidsTable { ids, ok := id.(string) if !ok || ids == "" { - l.ArgError(1, "each user id must be a string") + l.ArgError(2, "each user id must be a string") return 0 } uid, err := uuid.FromString(ids) if err != nil { - l.ArgError(1, "each user id must be a valid id") + l.ArgError(2, "each user id must be a valid id") return 0 } uids[uid] = struct{}{} @@ -5945,12 +5945,12 @@ func (n *RuntimeLuaNakamaModule) statusUnfollow(l *lua.LState) int { for _, id := range uidsTable { ids, ok := id.(string) if !ok || ids == "" { - l.ArgError(1, "each user id must be a string") + l.ArgError(2, "each user id must be a string") return 0 } uid, err := uuid.FromString(ids) if err != nil { - l.ArgError(1, "each user id must be a valid id") + l.ArgError(2, "each user id must be a valid id") return 0 } uids = append(uids, uid)