local nk = require("nakama") local user = {} function user.rpc_get_user_profile(context, payload) local request = nk.json_decode(payload or "{}") local targetUserId = request.user_id or context.user_id local status, account = pcall(nk.account_get_id, targetUserId) if not status then error("Account not found") end local metadata = {} if account.user.metadata then status, metadata = pcall(nk.json_decode, account.user.metadata) if not status then metadata = {} end end if metadata.banned and targetUserId == context.user_id then if metadata.ban_expires then -- Note: ban_expires stored as Unix time in Lua (seconds) or ISO string depending on how it was stored -- Let's check against current os.time() assuming Unix time local expiresAt = tonumber(metadata.ban_expires) if not expiresAt and type(metadata.ban_expires) == "string" then -- basic check if we stored iso string -- We assume it's valid ISO string and lua os.time might not parse it easily without custom function -- As a fallback, we'll keep the ban if we can't parse it error("Account banned until " .. metadata.ban_expires .. ". Reason: " .. (metadata.ban_reason or "")) end if expiresAt and expiresAt <= os.time() then metadata.banned = nil metadata.ban_reason = nil metadata.ban_expires = nil nk.account_update_id(targetUserId, nil, nil, nil, nil, nil, nil, nk.json_encode(metadata)) else error("Account banned until " .. tostring(metadata.ban_expires) .. ". Reason: " .. (metadata.ban_reason or "")) end else error("Account permanently banned. Reason: " .. (metadata.ban_reason or "")) end end return nk.json_encode({ user_id = account.user.id, username = account.user.username, display_name = account.user.display_name, avatar_url = account.user.avatar_url, create_time = account.user.create_time, role = metadata.role or "player" }) end function user.rpc_update_user_profile(context, payload) if not context.user_id then error("Not authenticated") end local request = nk.json_decode(payload) local status, err = pcall(nk.account_update_id, context.user_id, nil, request.display_name or nil, nil, nil, nil, request.avatar_url or nil, nil ) if not status then nk.logger_error("Failed to update profile: " .. tostring(err)) error("Failed to update profile") end return nk.json_encode({ success = true }) end function user.rpc_search_users(context, payload) if not context.user_id then error("Not authenticated") end local request = nk.json_decode(payload or "{}") local query = request.query or "" local users = {} local sql = "" local params = {} if query == "" then sql = "SELECT id, username, display_name, metadata FROM users WHERE id != '00000000-0000-0000-0000-000000000000' ORDER BY create_time DESC LIMIT 100" else sql = "SELECT id, username, display_name, metadata FROM users WHERE (username ILIKE $1 OR display_name ILIKE $1) AND id != '00000000-0000-0000-0000-000000000000' ORDER BY create_time DESC LIMIT 100" params = {"%" .. query .. "%"} end local status, rows = pcall(nk.sql_query, sql, params) if status and rows then for _, row in ipairs(rows) do local metadata = {} if row.metadata then local s, m = pcall(nk.json_decode, row.metadata) if s then metadata = m end end table.insert(users, { user_id = row.id, username = row.username or "", display_name = row.display_name or row.username or "", avatar_url = metadata.avatar_url or "" }) end end return nk.json_encode({ users = users }) end function user.rpc_change_credentials(context, payload) if not context.user_id then error("Not authenticated") end local req = nk.json_decode(payload or "{}") local account = nk.account_get_id(context.user_id) if account.email then if not req.current_password then error("Current password required") end local status = pcall(nk.authenticate_email, account.email, req.current_password, false) if not status then error("Incorrect current password.") end nk.unlink_email(context.user_id, account.email, req.current_password) end local status, err = pcall(nk.link_email, context.user_id, req.new_email, req.new_password) if not status then if account.email then pcall(nk.link_email, context.user_id, account.email, req.current_password) end error("Failed to set new credentials: " .. tostring(err)) end return nk.json_encode({ success = true }) end function user.rpc_send_lobby_invite(context, payload) if not context.user_id then error("Not authenticated") end local req = nk.json_decode(payload or "{}") if not req.to_user_id or not req.match_id then error("Missing to_user_id or match_id") end local sender = nk.account_get_id(context.user_id) local senderName = sender.user.display_name or sender.user.username or "Someone" nk.notification_send( req.to_user_id, senderName .. " invited you to their lobby", nk.json_encode({ match_id = req.match_id, from_name = senderName }), 1001, context.user_id, true ) nk.logger_info("Lobby invite sent from " .. context.user_id .. " to " .. req.to_user_id .. " for match " .. req.match_id) return nk.json_encode({ success = true }) end function user.rpc_send_friend_request(context, payload) if not context.user_id then error("Not authenticated") end local request = nk.json_decode(payload or "{}") local targetUserId = request.user_id or "" if targetUserId == "" then error("user_id is required") end if targetUserId == context.user_id then error("Cannot add yourself") end local senderAccount = nk.account_get_id(context.user_id) local senderName = senderAccount.user.display_name or senderAccount.user.username or "Someone" nk.notification_send( targetUserId, "Friend Request", nk.json_encode({ from_user_id = context.user_id, from_name = senderName }), 1002, context.user_id, true ) nk.logger_info("Friend request notification sent from " .. context.user_id .. " to " .. targetUserId) return nk.json_encode({ success = true }) end function user.after_authenticate(context, out, payload) if not context.user_id then return end -- We store the last 10 logins in user metadata or a dedicated collection local login_entry = { time = os.time(), ip = context.client_ip or "unknown" } local status, result = pcall(nk.storage_read, {{collection = "history", key = "logins", user_id = context.user_id}}) local logins = {} if status and result and #result > 0 then logins = result[1].value.logins or {} end table.insert(logins, 1, login_entry) -- Keep only last 20 logins to save space while #logins > 20 do table.remove(logins) end pcall(nk.storage_write, {{ collection = "history", key = "logins", user_id = context.user_id, value = { logins = logins }, permission_read = 0, permission_write = 0 }}) end function user.rpc_admin_get_user_history(context, payload) local utils = require("lua.utils") utils.require_admin(context) local request = nk.json_decode(payload or "{}") local targetUserId = request.user_id if not targetUserId then error("user_id is required") end local history = { wallet_ledger = {}, logins = {}, matches = {} } -- 1. Fetch Wallet Ledger (Economy History) local status_wallet, wallet_result = pcall(nk.wallet_ledger_list, targetUserId, 50) if status_wallet and wallet_result then history.wallet_ledger = wallet_result.items or {} end -- 2. Fetch Login History local status_logins, login_result = pcall(nk.storage_read, {{collection = "history", key = "logins", user_id = targetUserId}}) if status_logins and login_result and #login_result > 0 then history.logins = login_result[1].value.logins or {} end -- 3. Fetch Match History (If stored in collection 'matches') local status_matches, match_result = pcall(nk.storage_list, targetUserId, "matches", 50, "") if status_matches and match_result then for _, obj in ipairs(match_result.objects or {}) do table.insert(history.matches, obj.value) end end return nk.json_encode({ history = history }) end nk.register_rpc(user.rpc_get_user_profile, "get_user_profile") nk.register_rpc(user.rpc_update_user_profile, "update_user_profile") nk.register_rpc(user.rpc_search_users, "search_users") nk.register_rpc(user.rpc_change_credentials, "change_credentials") nk.register_rpc(user.rpc_send_lobby_invite, "send_lobby_invite") nk.register_rpc(user.rpc_send_friend_request, "send_friend_request") nk.register_rpc(user.rpc_admin_get_user_history, "admin_get_user_history") nk.register_req_after(user.after_authenticate, "AuthenticateDevice") nk.register_req_after(user.after_authenticate, "AuthenticateEmail") nk.register_req_after(user.after_authenticate, "AuthenticateCustom") nk.logger_info("LUA TEST: user module loaded") return user