diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 9767a1e2..1307ce7b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within @@ -126,4 +126,3 @@ enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. - diff --git a/README.md b/README.md index 80d996d6..ff3f1186 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,16 @@ It allows users to register with their LeetCode username and automatically fetch The goal of this project is to: -- Encourage consistent problem-solving among students -- Create a competitive yet motivating environment -- Provide visibility into individual coding progress +- Encourage consistent problem-solving among students +- Create a competitive yet motivating environment +- Provide visibility into individual coding progress --- - - ## Screenshots + A quick preview of the platform UI. The appearance may evolve as the project develops. + ### Home Page ![Home Page](assets/home-page.png) @@ -28,13 +28,10 @@ A quick preview of the platform UI. The appearance may evolve as the project dev ![Registration](assets/registration-page.png) - ### Leaderboard ![Leaderboard](assets/leaderboard.png) - - ## Related Repositories - [leetcode-ranking-data](https://github.com/codepvg/leetcode-ranking-data) – The database repository where raw JSON data and historical stats are stored @@ -65,6 +62,7 @@ leetcode-ranking/ ### 1. Fork and clone the repository First, fork the repository to your GitHub account. Then clone it locally: + ```bash git clone https://github.com/YOUR-USERNAME/leetcode-ranking.git cd leetcode-ranking @@ -82,9 +80,9 @@ or ## Usage -1. Open the registration page -2. Enter your name and LeetCode username -3. Submit the form +1. Open the registration page +2. Enter your name and LeetCode username +3. Submit the form 4. View your ranking on the leaderboard after the next sync --- @@ -93,7 +91,7 @@ or Contributions are welcome. -- Fork the repository -- Create a new branch -- Make your changes -- Submit a Pull Request +- Fork the repository +- Create a new branch +- Make your changes +- Submit a Pull Request diff --git a/package.json b/package.json index 73bfb2c8..25079eb8 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,10 @@ "name": "leetcode-ranking", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "server.js", "scripts": { - "dev": "node ./server.js" + "start": "node server.js", + "dev": "nodemon server.js" }, "repository": { "type": "git", diff --git a/scripts/fetch-student-info.js b/scripts/fetch-student-info.js new file mode 100644 index 00000000..4eee1465 --- /dev/null +++ b/scripts/fetch-student-info.js @@ -0,0 +1,89 @@ +function getFileName(daysAgo) { + const now = new Date(); + now.setDate(now.getDate() - daysAgo); + + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const date = String(now.getDate()).padStart(2, "0"); + let day = now.getDay(); + day = day === 0 ? 7 : day; + + return `${year}-${month}-${date}-${day}.json`; +} + +async function fetchStudentHistory(username) { + console.log("Fetching history for:", username); + + let history = []; + let missingFilesCount = 0; + const maxDays = 365; + const chunkSize = 100; + + let done = false; + + for (let chunkStart = 0; chunkStart < maxDays; chunkStart += chunkSize) { + if (done) break; + + const fetchPromises = []; + const chunkEnd = Math.min(chunkStart + chunkSize, maxDays); + + for (let daysAgo = chunkStart; daysAgo < chunkEnd; daysAgo++) { + const fileName = getFileName(daysAgo); + const rawUrl = `https://raw.githubusercontent.com/codepvg/leetcode-ranking-data/main/daily/${fileName}`; + + const p = fetch(rawUrl) + .then(async (res) => { + if (!res.ok) { + return { daysAgo, fileName, ok: false }; + } + const data = await res.json(); + return { daysAgo, fileName, ok: true, data }; + }) + .catch((err) => { + return { daysAgo, fileName, ok: false, error: err }; + }); + + fetchPromises.push(p); + } + + const results = await Promise.all(fetchPromises); + + for (const result of results) { + if (!result.ok) { + missingFilesCount++; + if (missingFilesCount >= 7) { + done = true; + break; + } + continue; + } + + missingFilesCount = 0; + + const user = result.data.find((u) => u.id === username); + + if (user) { + const dateStr = result.fileName.split("-").slice(0, 3).join("-"); + + history.push({ + date: dateStr, + easy: user.data.easySolved, + medium: user.data.mediumSolved, + hard: user.data.hardSolved, + }); + } else { + done = true; + break; + } + } + } + + history.sort((a, b) => new Date(a.date) - new Date(b.date)); + + return { + username, + history, + }; +} + +module.exports = fetchStudentHistory; diff --git a/server.js b/server.js index ea2087f2..298ff019 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,16 @@ const express = require("express"); const cors = require("cors"); const path = require("path"); -const fs = require("fs"); + const app = express(); const PORT = process.env.PORT || 3000; +const fetchStudentHistory = require("./scripts/fetch-student-info"); + app.use(cors()); app.use(express.static(path.join(__dirname, "frontend"))); +/* ---------------- HOME ROUTES ---------------- */ app.get("/", (req, res) => { res.sendFile(path.join(__dirname, "frontend", "index.html")); }); @@ -28,6 +31,32 @@ app.get("/uptime", (req, res) => { res.json({ status: "Website is running ✅" }); }); +const studentCache = new Map(); + +app.get("/api/student/:username", async (req, res) => { + const username = req.params.username; + + if (studentCache.has(username)) { + const cached = studentCache.get(username); + if (Date.now() - cached.timestamp < 5 * 60 * 1000) { + return res.json(cached.data); + } + } + + try { + const data = await fetchStudentHistory(username); + + studentCache.set(username, { timestamp: Date.now(), data }); + + res.json(data); + } catch (err) { + res.status(500).json({ + error: "Failed to fetch student details", + details: err.message, + }); + } +}); + app.use((req, res) => { res.status(404).send("Page not found"); });