From fb2826d043e367a20db6c737b84d29a7710614bd Mon Sep 17 00:00:00 2001 From: akshara200829-lgtm Date: Wed, 3 Jun 2026 14:09:55 +0530 Subject: [PATCH 1/6] feat: add rank change indicators to leaderboard --- frontend/leaderboard.html | 28 ++++++++++++++++++++++++++-- scripts/sync-leaderboard.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/frontend/leaderboard.html b/frontend/leaderboard.html index 242c6f83..be59d048 100644 --- a/frontend/leaderboard.html +++ b/frontend/leaderboard.html @@ -11,6 +11,20 @@ rel="stylesheet" /> + @@ -332,6 +346,16 @@

Leaderboard

`; renderLeaderboard(filteredData); } + function getRankChangeTag(rankChange) { + if (!rankChange) return ""; + if (rankChange === "NEW") + return `[NEW]`; + if (rankChange === "=") + return `[==]`; + if (rankChange.startsWith("+")) + return `[${rankChange}]`; + return `[${rankChange}]`; +} function getRankTag(rank) { switch (rank) { case 1: @@ -390,7 +414,7 @@

Leaderboard

const row = document.createElement("div"); row.className = "leaderboard-row"; row.innerHTML = ` -
${rank}
+
${rank}${getRankChangeTag(user.rankChange)}
${tag}${user.name}
${user.id} @@ -430,7 +454,7 @@

Leaderboard

card.className = "mobile-card"; card.innerHTML = `
-
#${rank}
+
#${rank}${getRankChangeTag(user.rankChange)}
${user.score} diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 88de0d03..03cf2a62 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -28,6 +28,30 @@ function getFileName(daysAgo) { return `${year}-${month}-${date}-${day}.json`; } +function computeRankChanges(currentSorted, previousSortedFilepath) { + let previousRanks = {}; + try { + const raw = fs.readFileSync(previousSortedFilepath, "utf8"); + const previousSorted = JSON.parse(raw); + previousSorted.forEach((user, idx) => { + previousRanks[user.id] = idx + 1; + }); + } catch { + // File doesn't exist yet (first run) — everyone gets NEW + } + + currentSorted.forEach((user, idx) => { + const currentRank = idx + 1; + if (previousRanks[user.id] === undefined) { + user.rankChange = "NEW"; + } else { + const delta = previousRanks[user.id] - currentRank; + if (delta > 0) user.rankChange = `+${delta}`; + else if (delta < 0) user.rankChange = `${delta}`; + else user.rankChange = "="; + } + }); +} (async () => { const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "..", "data"); @@ -87,6 +111,7 @@ function getFileName(daysAgo) { overallData.sort((a, b) => b.score - a.score); console.log("Writing sorted daily data to overall file..."); const overallFilepath = path.join(DATA_DIR, "overall.json"); + computeRankChanges(overallData, overallFilepath); try { fs.writeFileSync( overallFilepath, @@ -144,6 +169,7 @@ function getFileName(daysAgo) { console.log("Writing sorted daily data to daily.json..."); const dailyFilepath = path.join(DATA_DIR, "daily.json"); + computeRankChanges(dailyData, dailyFilepath); try { fs.writeFileSync(dailyFilepath, JSON.stringify(dailyData, null, 2), "utf8"); console.log("Daily data saved successfully"); @@ -199,6 +225,7 @@ function getFileName(daysAgo) { console.log("Writing sorted weekly data to weekly.json..."); const weeklyFilepath = path.join(DATA_DIR, "weekly.json"); + computeRankChanges(weeklyData, weeklyFilepath); try { fs.writeFileSync( weeklyFilepath, @@ -258,6 +285,7 @@ function getFileName(daysAgo) { console.log("Writing sorted monthly data to monthly.json..."); const monthlyFilepath = path.join(DATA_DIR, "monthly.json"); + computeRankChanges(monthlyData, monthlyFilepath); try { fs.writeFileSync( monthlyFilepath, @@ -291,4 +319,4 @@ function getFileName(daysAgo) { } catch (err) { console.error(`Failed to write sync file: `, err.message); } -})(); +})(); \ No newline at end of file From 2e79460bb9257b8fcfc1a23fb083f6c343a1d099 Mon Sep 17 00:00:00 2001 From: akshara200829-lgtm Date: Fri, 5 Jun 2026 14:13:14 +0530 Subject: [PATCH 2/6] fix: address reviewer feedback on rank change indicators --- frontend/leaderboard.html | 13 ++++++------- scripts/sync-leaderboard.js | 34 ++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/frontend/leaderboard.html b/frontend/leaderboard.html index be59d048..c855c286 100644 --- a/frontend/leaderboard.html +++ b/frontend/leaderboard.html @@ -23,7 +23,6 @@ .rank-up { color: var(--green); } .rank-down { color: var(--red); } .rank-neutral { color: var(--text-muted); } - .rank-new { color: var(--amber); } @@ -349,7 +348,7 @@

Leaderboard

function getRankChangeTag(rankChange) { if (!rankChange) return ""; if (rankChange === "NEW") - return `[NEW]`; + return `[new]`; if (rankChange === "=") return `[==]`; if (rankChange.startsWith("+")) @@ -414,8 +413,8 @@

Leaderboard

const row = document.createElement("div"); row.className = "leaderboard-row"; row.innerHTML = ` -
${rank}${getRankChangeTag(user.rankChange)}
-
${tag}${user.name}
+
${rank}
+
${tag}${user.name}${getRankChangeTag(user.rankChange)}
${user.id} Leaderboard card.className = "mobile-card"; card.innerHTML = `
-
#${rank}${getRankChangeTag(user.rankChange)}
+
#${rank}
${user.score} @@ -477,7 +476,7 @@

Leaderboard

-
${tag}${user.name}
+
${tag}${user.name}${getRankChangeTag(user.rankChange)}
${user.id} Leaderboard - + \ No newline at end of file diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 03cf2a62..630dd3e4 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -28,10 +28,31 @@ function getFileName(daysAgo) { return `${year}-${month}-${date}-${day}.json`; } -function computeRankChanges(currentSorted, previousSortedFilepath) { + +function getSnapshotPath(dataDir) { + const d = new Date(); + d.setDate(d.getDate() - 1); + const date = d.toISOString().split("T")[0]; + return path.join(dataDir, "snapshots", `${date}.json`); +} + +function saveSnapshotIfNeeded(dataDir, overallData) { + const snapshotsDir = path.join(dataDir, "snapshots"); + if (!fs.existsSync(snapshotsDir)) fs.mkdirSync(snapshotsDir); + + const today = new Date().toISOString().split("T")[0]; + const todaySnapshot = path.join(snapshotsDir, `${today}.json`); + if (!fs.existsSync(todaySnapshot)) { + fs.writeFileSync(todaySnapshot, JSON.stringify(overallData, null, 2), "utf8"); + console.log("Daily snapshot saved."); + } +} + +function computeRankChanges(currentSorted, dataDir) { let previousRanks = {}; try { - const raw = fs.readFileSync(previousSortedFilepath, "utf8"); + const snapshotPath = getSnapshotPath(dataDir); + const raw = fs.readFileSync(snapshotPath, "utf8"); const previousSorted = JSON.parse(raw); previousSorted.forEach((user, idx) => { previousRanks[user.id] = idx + 1; @@ -111,7 +132,8 @@ function computeRankChanges(currentSorted, previousSortedFilepath) { overallData.sort((a, b) => b.score - a.score); console.log("Writing sorted daily data to overall file..."); const overallFilepath = path.join(DATA_DIR, "overall.json"); - computeRankChanges(overallData, overallFilepath); + saveSnapshotIfNeeded(DATA_DIR, overallData); + computeRankChanges(overallData, DATA_DIR); try { fs.writeFileSync( overallFilepath, @@ -169,7 +191,7 @@ function computeRankChanges(currentSorted, previousSortedFilepath) { console.log("Writing sorted daily data to daily.json..."); const dailyFilepath = path.join(DATA_DIR, "daily.json"); - computeRankChanges(dailyData, dailyFilepath); + computeRankChanges(dailyData, DATA_DIR); try { fs.writeFileSync(dailyFilepath, JSON.stringify(dailyData, null, 2), "utf8"); console.log("Daily data saved successfully"); @@ -225,7 +247,7 @@ function computeRankChanges(currentSorted, previousSortedFilepath) { console.log("Writing sorted weekly data to weekly.json..."); const weeklyFilepath = path.join(DATA_DIR, "weekly.json"); - computeRankChanges(weeklyData, weeklyFilepath); + computeRankChanges(weeklyData, DATA_DIR); try { fs.writeFileSync( weeklyFilepath, @@ -285,7 +307,7 @@ function computeRankChanges(currentSorted, previousSortedFilepath) { console.log("Writing sorted monthly data to monthly.json..."); const monthlyFilepath = path.join(DATA_DIR, "monthly.json"); - computeRankChanges(monthlyData, monthlyFilepath); + computeRankChanges(monthlyData, DATA_DIR); try { fs.writeFileSync( monthlyFilepath, From 33df1f96b0eecba11c494271bb8424486012bd3c Mon Sep 17 00:00:00 2001 From: akshara200829-lgtm Date: Fri, 5 Jun 2026 14:20:15 +0530 Subject: [PATCH 3/6] style: format files with prettier --- frontend/leaderboard.html | 50 +++++++++++++++++++++---------------- scripts/sync-leaderboard.js | 8 ++++-- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/frontend/leaderboard.html b/frontend/leaderboard.html index c855c286..0119ac10 100644 --- a/frontend/leaderboard.html +++ b/frontend/leaderboard.html @@ -12,18 +12,24 @@ /> + .rank-change { + display: inline-block; + font-size: 0.65rem; + font-family: "Fira Code", monospace; + margin-left: 0.4rem; + vertical-align: middle; + opacity: 0.85; + } + .rank-up { + color: var(--green); + } + .rank-down { + color: var(--red); + } + .rank-neutral { + color: var(--text-muted); + } + @@ -346,15 +352,15 @@

Leaderboard

renderLeaderboard(filteredData); } function getRankChangeTag(rankChange) { - if (!rankChange) return ""; - if (rankChange === "NEW") - return `[new]`; - if (rankChange === "=") - return `[==]`; - if (rankChange.startsWith("+")) - return `[${rankChange}]`; - return `[${rankChange}]`; -} + if (!rankChange) return ""; + if (rankChange === "NEW") + return `[new]`; + if (rankChange === "=") + return `[==]`; + if (rankChange.startsWith("+")) + return `[${rankChange}]`; + return `[${rankChange}]`; + } function getRankTag(rank) { switch (rank) { case 1: @@ -590,4 +596,4 @@

Leaderboard

- \ No newline at end of file + diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 630dd3e4..6a042578 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -43,7 +43,11 @@ function saveSnapshotIfNeeded(dataDir, overallData) { const today = new Date().toISOString().split("T")[0]; const todaySnapshot = path.join(snapshotsDir, `${today}.json`); if (!fs.existsSync(todaySnapshot)) { - fs.writeFileSync(todaySnapshot, JSON.stringify(overallData, null, 2), "utf8"); + fs.writeFileSync( + todaySnapshot, + JSON.stringify(overallData, null, 2), + "utf8", + ); console.log("Daily snapshot saved."); } } @@ -341,4 +345,4 @@ function computeRankChanges(currentSorted, dataDir) { } catch (err) { console.error(`Failed to write sync file: `, err.message); } -})(); \ No newline at end of file +})(); From 8f7b35854c24c2e0c7b94e7054a06abe0d29ceef Mon Sep 17 00:00:00 2001 From: Jagdish Prajapati Date: Fri, 5 Jun 2026 18:49:04 +0530 Subject: [PATCH 4/6] Use GitHub history for rank comparisons --- scripts/sync-leaderboard.js | 67 +++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 6a042578..888596b6 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -29,44 +29,54 @@ function getFileName(daysAgo) { return `${year}-${month}-${date}-${day}.json`; } -function getSnapshotPath(dataDir) { +async function getYesterdaySnapshot(filePath) { + const owner = "codepvg"; + const repo = "leetcode-ranking-data"; const d = new Date(); d.setDate(d.getDate() - 1); - const date = d.toISOString().split("T")[0]; - return path.join(dataDir, "snapshots", `${date}.json`); -} + const targetDate = d.toISOString().split("T")[0]; + const until = `${targetDate}T23:59:59Z`; + const commitsUrl = `https://api.github.com/repos/${owner}/${repo}/commits?path=${filePath}&until=${until}&per_page=1`; -function saveSnapshotIfNeeded(dataDir, overallData) { - const snapshotsDir = path.join(dataDir, "snapshots"); - if (!fs.existsSync(snapshotsDir)) fs.mkdirSync(snapshotsDir); + const headers = { "User-Agent": "CodePVG-App" }; + if (process.env.DATA_REPO_TOKEN) { + headers["Authorization"] = `token ${process.env.DATA_REPO_TOKEN}`; + } - const today = new Date().toISOString().split("T")[0]; - const todaySnapshot = path.join(snapshotsDir, `${today}.json`); - if (!fs.existsSync(todaySnapshot)) { - fs.writeFileSync( - todaySnapshot, - JSON.stringify(overallData, null, 2), - "utf8", - ); - console.log("Daily snapshot saved."); + try { + const commitResponse = await axios.get(commitsUrl, { headers }); + const commits = commitResponse.data; + + if (!commits || commits.length === 0) { + console.warn(`No commits found for ${filePath} on or before ${targetDate}.`); + return null; + } + + const yesterdaySHA = commits[0].sha; + console.log(`📌 Using Commit for ${filePath}: "${commits[0].commit.message}" (SHA: ${yesterdaySHA})`); + + const rawFileUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${yesterdaySHA}/${filePath}`; + const fileResponse = await axios.get(rawFileUrl); + return fileResponse.data; + } catch (error) { + console.error(`Error fetching historical data for ${filePath}:`, error.message); + return null; } } -function computeRankChanges(currentSorted, dataDir) { +async function computeRankChanges(currentSorted, filename) { let previousRanks = {}; - try { - const snapshotPath = getSnapshotPath(dataDir); - const raw = fs.readFileSync(snapshotPath, "utf8"); - const previousSorted = JSON.parse(raw); - previousSorted.forEach((user, idx) => { + const previousData = await getYesterdaySnapshot(filename); + + if (previousData && Array.isArray(previousData)) { + previousData.forEach((user, idx) => { previousRanks[user.id] = idx + 1; }); - } catch { - // File doesn't exist yet (first run) — everyone gets NEW } currentSorted.forEach((user, idx) => { const currentRank = idx + 1; + if (previousRanks[user.id] === undefined) { user.rankChange = "NEW"; } else { @@ -136,8 +146,7 @@ function computeRankChanges(currentSorted, dataDir) { overallData.sort((a, b) => b.score - a.score); console.log("Writing sorted daily data to overall file..."); const overallFilepath = path.join(DATA_DIR, "overall.json"); - saveSnapshotIfNeeded(DATA_DIR, overallData); - computeRankChanges(overallData, DATA_DIR); + await computeRankChanges(overallData, "overall.json"); try { fs.writeFileSync( overallFilepath, @@ -195,7 +204,7 @@ function computeRankChanges(currentSorted, dataDir) { console.log("Writing sorted daily data to daily.json..."); const dailyFilepath = path.join(DATA_DIR, "daily.json"); - computeRankChanges(dailyData, DATA_DIR); + await computeRankChanges(dailyData, "daily.json"); try { fs.writeFileSync(dailyFilepath, JSON.stringify(dailyData, null, 2), "utf8"); console.log("Daily data saved successfully"); @@ -251,7 +260,7 @@ function computeRankChanges(currentSorted, dataDir) { console.log("Writing sorted weekly data to weekly.json..."); const weeklyFilepath = path.join(DATA_DIR, "weekly.json"); - computeRankChanges(weeklyData, DATA_DIR); + await computeRankChanges(weeklyData, "weekly.json"); try { fs.writeFileSync( weeklyFilepath, @@ -311,7 +320,7 @@ function computeRankChanges(currentSorted, dataDir) { console.log("Writing sorted monthly data to monthly.json..."); const monthlyFilepath = path.join(DATA_DIR, "monthly.json"); - computeRankChanges(monthlyData, DATA_DIR); + await computeRankChanges(monthlyData, "monthly.json"); try { fs.writeFileSync( monthlyFilepath, From 374e118b9ee847bc85380e36329c8dc21375dc74 Mon Sep 17 00:00:00 2001 From: Jagdish Prajapati Date: Fri, 5 Jun 2026 18:50:25 +0530 Subject: [PATCH 5/6] Add DATA_REPO_TOKEN to sync-leaderboard workflow --- .github/workflows/sync-leaderboard.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-leaderboard.yml b/.github/workflows/sync-leaderboard.yml index c2ca5952..44b0e87c 100644 --- a/.github/workflows/sync-leaderboard.yml +++ b/.github/workflows/sync-leaderboard.yml @@ -55,6 +55,7 @@ jobs: # 2. Run sync export DATA_DIR=db-repo + export DATA_REPO_TOKEN=${{ secrets.DATA_REPO_TOKEN }} node scripts/sync-leaderboard.js # 3. Stage changes (everything except users.json) From 136ef3869abbb0b9bbebc28b7d212fd7d2c2ba6a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Jun 2026 13:21:57 +0000 Subject: [PATCH 6/6] style: auto-format code with Prettier (/format) --- scripts/sync-leaderboard.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 888596b6..c50ec86f 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -48,18 +48,25 @@ async function getYesterdaySnapshot(filePath) { const commits = commitResponse.data; if (!commits || commits.length === 0) { - console.warn(`No commits found for ${filePath} on or before ${targetDate}.`); + console.warn( + `No commits found for ${filePath} on or before ${targetDate}.`, + ); return null; } const yesterdaySHA = commits[0].sha; - console.log(`📌 Using Commit for ${filePath}: "${commits[0].commit.message}" (SHA: ${yesterdaySHA})`); + console.log( + `📌 Using Commit for ${filePath}: "${commits[0].commit.message}" (SHA: ${yesterdaySHA})`, + ); const rawFileUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${yesterdaySHA}/${filePath}`; const fileResponse = await axios.get(rawFileUrl); return fileResponse.data; } catch (error) { - console.error(`Error fetching historical data for ${filePath}:`, error.message); + console.error( + `Error fetching historical data for ${filePath}:`, + error.message, + ); return null; } }