From 3b129b95251e179f23302d47550109b5c793f4f1 Mon Sep 17 00:00:00 2001 From: Yashaswini K P Date: Mon, 22 Jun 2026 21:41:22 +0530 Subject: [PATCH] feat: pre-calculate leaderboard ranks and update user endpoints and UI --- scripts/fetch-user-info.js | 19 ++++++- scripts/sync-leaderboard.js | 106 ++++++++++++++++++++++++++++++++---- 2 files changed, 114 insertions(+), 11 deletions(-) diff --git a/scripts/fetch-user-info.js b/scripts/fetch-user-info.js index a60e5f55..a1e073df 100644 --- a/scripts/fetch-user-info.js +++ b/scripts/fetch-user-info.js @@ -22,12 +22,28 @@ async function fetchUserInfo(username) { ); } + let leaderboardRanks = { + overall: { rank: "--", change: 0 }, + daily: { rank: "--", change: 0 }, + weekly: { rank: "--", change: 0 }, + monthly: { rank: "--", change: 0 }, + }; + try { const cacheBuster = Date.now(); const rawUrl = `https://raw.githubusercontent.com/codepvg/leetcode-ranking-data/main/user-data/${username}.json?t=${cacheBuster}`; const response = await fetch(rawUrl); if (response.ok) { - history = await response.json(); + const data = await response.json(); + if (Array.isArray(data)) { + history = data; + } else { + // If data is our new object structure, pull both properties safely + history = data.history || []; + if (data.leaderboardRanks) { + leaderboardRanks = data.leaderboardRanks; + } + } } else { console.warn( `No historical data found for user: ${username} (HTTP ${response.status})`, @@ -46,6 +62,7 @@ async function fetchUserInfo(username) { return { username, ranking, + leaderboardRanks, history, }; } diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 64a6829b..d0221d92 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -51,11 +51,17 @@ function updateUserHistory(user, DATA_DIR) { } const userHistoryPath = path.join(historyDir, `${user.id}.json`); - let history = []; + let profileData = { leaderboardRanks: {}, history: [] }; if (fs.existsSync(userHistoryPath)) { try { - history = JSON.parse(fs.readFileSync(userHistoryPath, "utf8")); + const rawData = JSON.parse(fs.readFileSync(userHistoryPath, "utf8")); + if (Array.isArray(rawData)) { + profileData.history = rawData; + } else { + profileData = rawData; + if (!profileData.history) profileData.history = []; + } } catch (err) { console.error( `Failed to parse history for ${user.id}, resetting:`, @@ -65,7 +71,9 @@ function updateUserHistory(user, DATA_DIR) { } const dateStr = getFileName(0).split("-").slice(0, 3).join("-"); - const existingIndex = history.findIndex((entry) => entry.date === dateStr); + const existingIndex = profileData.history.findIndex( + (entry) => entry.date === dateStr, + ); const newEntry = { date: dateStr, @@ -75,14 +83,14 @@ function updateUserHistory(user, DATA_DIR) { }; if (existingIndex !== -1) { - history[existingIndex] = newEntry; + profileData.history[existingIndex] = newEntry; } else { - history.push(newEntry); + profileData.history.push(newEntry); } - history.sort((a, b) => new Date(a.date) - new Date(b.date)); + profileData.history.sort((a, b) => new Date(a.date) - new Date(b.date)); - atomicWrite(userHistoryPath, history); + atomicWrite(userHistoryPath, profileData); } function assignCompetitionRanks(sortedData) { @@ -228,6 +236,7 @@ async function processTimeframe(sourceData, DATA_DIR, periodName, daysAgo) { try { atomicWrite(filepath, data); console.log(`${periodName} data saved successfully`); + return data; } catch (err) { console.error(`Failed to write json file: `, err.message); process.exit(1); @@ -394,9 +403,86 @@ async function processTimeframe(sourceData, DATA_DIR, periodName, daysAgo) { } // Process timeframe-based leaderboards using the shared function - await processTimeframe(overallData, DATA_DIR, "daily", 1); - await processTimeframe(overallData, DATA_DIR, "weekly", 7); - await processTimeframe(overallData, DATA_DIR, "monthly", 30); + const dailyData = await processTimeframe(overallData, DATA_DIR, "daily", 1); + const weeklyData = await processTimeframe(overallData, DATA_DIR, "weekly", 7); + const monthlyData = await processTimeframe( + overallData, + DATA_DIR, + "monthly", + 30, + ); + + const overallMap = new Map( + overallData.map((u) => [ + u.id, + { rank: u.originalRank || "--", change: u.rankChange || "=" }, + ]), + ); + const dailyMap = new Map( + dailyData.map((u) => [ + u.id, + { rank: u.originalRank || "--", change: u.rankChange || "=" }, + ]), + ); + const weeklyMap = new Map( + weeklyData.map((u) => [ + u.id, + { rank: u.originalRank || "--", change: u.rankChange || "=" }, + ]), + ); + const monthlyMap = new Map( + monthlyData.map((u) => [ + u.id, + { rank: u.originalRank || "--", change: u.rankChange || "=" }, + ]), + ); + + const formatChange = (changeStr) => { + if (changeStr === "=" || changeStr === "NEW") return 0; + return parseInt(changeStr, 10) || 0; + }; + + overallData.forEach((user) => { + const userHistoryPath = path.join(DATA_DIR, "user-data", `${user.id}.json`); + if (!fs.existsSync(userHistoryPath)) return; + + try { + const currentProfile = JSON.parse( + fs.readFileSync(userHistoryPath, "utf8"), + ); + + const overallInfo = overallMap.get(user.id) || { + rank: "--", + change: "=", + }; + const dailyInfo = dailyMap.get(user.id) || { rank: "--", change: "=" }; + const weeklyInfo = weeklyMap.get(user.id) || { rank: "--", change: "=" }; + const monthlyInfo = monthlyMap.get(user.id) || { + rank: "--", + change: "=", + }; + + currentProfile.leaderboardRanks = { + overall: { + rank: overallInfo.rank, + change: formatChange(overallInfo.change), + }, + daily: { rank: dailyInfo.rank, change: formatChange(dailyInfo.change) }, + weekly: { + rank: weeklyInfo.rank, + change: formatChange(weeklyInfo.change), + }, + monthly: { + rank: monthlyInfo.rank, + change: formatChange(monthlyInfo.change), + }, + }; + + atomicWrite(userHistoryPath, currentProfile); + } catch (err) { + console.error(`Failed to inject ranks for user ${user.id}:`, err.message); + } + }); console.log("Generating changes.json..."); const changesFilepath = path.join(DATA_DIR, "changes.json");