feat : update backend

This commit is contained in:
2026-04-08 03:12:55 +08:00
parent 7e22f48c57
commit e222cc49ee
11 changed files with 619 additions and 935 deletions
+145 -2
View File
@@ -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);