feat : update backend
This commit is contained in:
@@ -28,10 +28,15 @@ function InitModule(ctx, logger, nk, initializer) {
|
||||
initializer.registerRpc("admin_update_stats", rpcAdminUpdateStats);
|
||||
initializer.registerRpc("admin_delete_stats", rpcAdminDeleteStats);
|
||||
initializer.registerRpc("admin_sync_leaderboard", rpcAdminSyncLeaderboard);
|
||||
// Client-accessible score submission (authoritative leaderboard requires server-side writes)
|
||||
initializer.registerRpc("submit_score", rpcSubmitScore);
|
||||
initializer.registerRpc("sync_leaderboard", rpcSyncLeaderboard);
|
||||
initializer.registerRpc("change_credentials", rpcChangeCredentials);
|
||||
initializer.registerRpc("reset_stats", rpcResetStats);
|
||||
|
||||
// Create default native leaderboard
|
||||
// id: "global_high_score", authoritative: true, sort: "desc", operator: "best", reset: None
|
||||
nk.leaderboardCreate("global_high_score", true, "desc", "best", null, {});
|
||||
try { nk.leaderboardCreate("global_high_score", true, "desc", "best", null, {}); } catch(e) {}
|
||||
|
||||
logger.info("Tekton admin module loaded");
|
||||
}
|
||||
@@ -477,6 +482,144 @@ function rpcGetLeaderboardStats(ctx, logger, nk, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
// Any authenticated user can submit their own score (server writes on their behalf)
|
||||
function rpcSubmitScore(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
try {
|
||||
var request = JSON.parse(payload || "{}");
|
||||
var score = parseInt(request.score) || 0;
|
||||
var account = nk.accountGetId(ctx.userId);
|
||||
var metadata = {
|
||||
games_played: request.games_played || 0,
|
||||
games_won: request.games_won || 0,
|
||||
avatar_url: account.user.avatarUrl || request.avatar_url || ""
|
||||
};
|
||||
nk.leaderboardRecordWrite(
|
||||
"global_high_score",
|
||||
ctx.userId,
|
||||
account.user.username,
|
||||
score,
|
||||
0,
|
||||
metadata
|
||||
);
|
||||
logger.info("Score submitted for user " + ctx.userId + ": " + score);
|
||||
return JSON.stringify({ success: true });
|
||||
} catch (e) {
|
||||
logger.error("Failed to submit score for " + ctx.userId + ": " + e);
|
||||
throw new Error("Failed to submit score: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Any authenticated user can trigger a bulk sync (reads all public stats, populates leaderboard)
|
||||
function rpcSyncLeaderboard(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
try {
|
||||
var result = nk.storageList(null, "stats", 100, "");
|
||||
var statsObjects = result.objects || [];
|
||||
var userGroup = {};
|
||||
|
||||
for (var i = 0; i < statsObjects.length; i++) {
|
||||
var obj = statsObjects[i];
|
||||
var userId = obj.userId;
|
||||
var value;
|
||||
try {
|
||||
// obj.value may already be an object or a string depending on Nakama version
|
||||
value = (typeof obj.value === "string") ? JSON.parse(obj.value) : obj.value;
|
||||
} catch (e) { continue; }
|
||||
if (!value) continue;
|
||||
|
||||
if (!userGroup[userId]) {
|
||||
userGroup[userId] = {
|
||||
high_score: value.high_score || 0,
|
||||
games_played: value.games_played || 0,
|
||||
games_won: value.games_won || 0,
|
||||
avatar_url: value.avatar_url || ""
|
||||
};
|
||||
} else {
|
||||
userGroup[userId].high_score = Math.max(userGroup[userId].high_score, value.high_score || 0);
|
||||
userGroup[userId].games_played = Math.max(userGroup[userId].games_played, value.games_played || 0);
|
||||
userGroup[userId].games_won = Math.max(userGroup[userId].games_won, value.games_won || 0);
|
||||
}
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var debugLogs = [];
|
||||
for (var uid in userGroup) {
|
||||
try {
|
||||
var stats = userGroup[uid];
|
||||
var account = nk.accountGetId(uid);
|
||||
var meta = {
|
||||
games_played: stats.games_played || 0,
|
||||
games_won: stats.games_won || 0,
|
||||
avatar_url: stats.avatar_url || account.user.avatarUrl || ""
|
||||
};
|
||||
nk.leaderboardRecordWrite("global_high_score", uid, account.user.username, stats.high_score, 0, meta);
|
||||
count++;
|
||||
} catch (inner) {
|
||||
debugLogs.push("Error user " + uid + ": " + inner);
|
||||
logger.error("Failed to sync record for " + uid + ": " + inner);
|
||||
}
|
||||
}
|
||||
logger.info("Synced " + count + " records to leaderboard by user " + ctx.userId);
|
||||
return JSON.stringify({ success: true, synced: count, objects_found: statsObjects.length, debug: debugLogs });
|
||||
} catch (e) {
|
||||
logger.error("Leaderboard sync failed: " + e);
|
||||
throw new Error("Sync failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Change Email / Password securely
|
||||
function rpcChangeCredentials(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
var req = {};
|
||||
try { req = JSON.parse(payload || "{}"); } catch (e) {}
|
||||
|
||||
var account = nk.accountGetId(ctx.userId);
|
||||
|
||||
// If not a guest (has email), verify current password and unlink
|
||||
if (account.email) {
|
||||
if (!req.current_password) throw new Error("Current password required");
|
||||
try {
|
||||
nk.authenticateEmail(account.email, req.current_password, false);
|
||||
} catch (e) {
|
||||
throw new Error("Incorrect current password.");
|
||||
}
|
||||
nk.unlinkEmail(ctx.userId, account.email, req.current_password);
|
||||
}
|
||||
|
||||
try {
|
||||
nk.linkEmail(ctx.userId, req.new_email, req.new_password);
|
||||
} catch (e) {
|
||||
// Safe rollback
|
||||
if (account.email) nk.linkEmail(ctx.userId, account.email, req.current_password);
|
||||
throw new Error("Failed to set new credentials: " + e.message);
|
||||
}
|
||||
|
||||
return JSON.stringify({ success: true });
|
||||
}
|
||||
|
||||
// Reset Game Stats
|
||||
function rpcResetStats(ctx, logger, nk, payload) {
|
||||
if (!ctx.userId) throw new Error("Not authenticated");
|
||||
var account = nk.accountGetId(ctx.userId);
|
||||
|
||||
// Delete native leaderboard rank
|
||||
try { nk.leaderboardRecordDelete("global_high_score", ctx.userId); } catch (e) {}
|
||||
|
||||
// Wipe storage stats
|
||||
var zeros = { games_played: 0, games_won: 0, high_score: 0, total_kills: 0, total_deaths: 0 };
|
||||
nk.storageWrite([{
|
||||
collection: "stats",
|
||||
key: "game_stats",
|
||||
userId: ctx.userId,
|
||||
value: zeros,
|
||||
permissionRead: 2,
|
||||
permissionWrite: 1
|
||||
}]);
|
||||
|
||||
return JSON.stringify({ success: true });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Admin User Management RPCs
|
||||
// =============================================================================
|
||||
@@ -680,7 +823,7 @@ function rpcAdminSyncLeaderboard(ctx, logger, nk, payload) {
|
||||
avatar_url: stats.avatar_url || account.user.avatarUrl || ""
|
||||
};
|
||||
|
||||
nk.leaderboardRecordWrite("global_high_score", userId, account.user.username, stats.high_score, 0, JSON.stringify(metadata));
|
||||
nk.leaderboardRecordWrite("global_high_score", userId, account.user.username, stats.high_score, 0, metadata);
|
||||
count++;
|
||||
} catch (inner) {
|
||||
logger.error("Failed to sync merged record for " + userId + ": " + inner);
|
||||
|
||||
Reference in New Issue
Block a user