Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/coverage/prettify.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions frontend/src/pages/CodeforcesPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ function ActivityHeatmap({ dailyActivity = {} }) {
<div>
<div className="overflow-x-auto pb-2">
<div className="flex gap-1 min-w-max">
{weeks.map((week, wi) => (
{(weeks ?? []).map((week, wi) => (
<div key={wi} className="flex flex-col gap-1">
{week.map(({ key, count }) => (
{(week ?? []).map(({ key, count }) => (
<div
key={key}
title={`${key}: ${count} submission${count !== 1 ? "s" : ""}`}
Expand Down Expand Up @@ -496,7 +496,7 @@ export default function CodeforcesPage() {
.sort(([a], [b]) => {
if (a === "unrated") return 1;
if (b === "unrated") return -1;
return parseInt(a) - parseInt(b);
return parseInt(a, 10) - parseInt(b, 10);
})
.map(([rating, count]) => {
const max = Math.max(...Object.values(stats.byRating || {}).map(Number));
Expand Down
6 changes: 3 additions & 3 deletions server/modules/ai/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ CODEFORCES TELEMETRY:
- Difficulty Distribution: ${
Object.entries(profile.stats?.byRating || {})
.filter(([, v]) => v > 0)
.sort(([a], [b]) => parseInt(a) - parseInt(b))
.sort(([a], [b]) => parseInt(a, 10) - parseInt(b, 10))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node <<'NODE'
const keys = ["unrated", "800", "1200", "2500plus"];
const sorted = [...keys].sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
console.log(sorted);
NODE

Repository: kunalverma2512/CodeLens

Length of output: 202


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== Locate files =="
git ls-files | rg '(^|/)server/modules/ai/service\.js$|frontend/src/pages/CodeforcesPage\.jsx$' || true

echo "== ai/service.js around line 47 =="
if [ -f server/modules/ai/service.js ]; then
  nl -ba server/modules/ai/service.js | sed -n '35,55p'
fi

echo "== CodeforcesPage sentinel handling =="
if [ -f frontend/src/pages/CodeforcesPage.jsx ]; then
  nl -ba frontend/src/pages/CodeforcesPage.jsx | sed -n '480,510p'
fi

echo "== byRating references =="
rg -n 'byRating|unrated|sort\(\[' server frontend -S || true

echo "== JS comparator behavior for NaN =="
node - <<'NODE'
const keys = ["unrated", "800", "1200", "2500plus", "2800", "3000"];
for (const use of [
  "original",
  "numeric-unrated-sentinel",
  "unrated-then-numeric"
]) {
  let sorted;
  if (use === "original") {
    sorted = [...keys].sort(([a], [b]) => parseInt(a, 10) - parseInt(b, 10));
  } else if (use === "numeric-unrated-sentinel") {
    sorted = [...keys].sort(([a], [b]) => {
      if (a === b) return 0;
      if (a === "unrated") return 1;
      if (b === "unrated") return -1;
      return parseInt(a, 10) - parseInt(b, 10);
    });
  } else {
    sorted = [...keys].sort(([a], [b]) => {
      if (a === "unrated") return -1;
      if (b === "unrated") return 1;
      return Number(a) - Number(2500) - (Number(b) - Number(2500));
    });
  }
  console.log(use, JSON.stringify(sorted), sorted.map(x => (x === "unrated" ? "unrated" : Number(x))));
}
NODE

Repository: kunalverma2512/CodeLens

Length of output: 322


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== ai/service.js around line 47 =="
if [ -f server/modules/ai/service.js ]; then
  awk 'NR>=35 && NR<=55 { printf "%6d\t%s\n", NR, $0 }' server/modules/ai/service.js
fi

echo "== CodeforcesPage sentinel handling =="
if [ -f frontend/src/pages/CodeforcesPage.jsx ]; then
  awk 'NR>=480 && NR<=510 { printf "%6d\t%s\n", NR, $0 }' frontend/src/pages/CodeforcesPage.jsx
fi

echo "== byRating references =="
rg -n 'byRating|unrated|sort\(\[' server frontend -S || true

echo "== JS comparator behavior for NaN =="
node - <<'NODE'
const keys = ["unrated", "800", "1200", "2500plus", "2800", "3000"];
for (const use of [
  "original",
  "numeric-unrated-sentinel",
]) {
  let sorted;
  if (use === "original") {
    sorted = [...keys].sort(([a], [b]) => parseInt(a, 10) - parseInt(b, 10));
  } else if (use === "numeric-unrated-sentinel") {
    sorted = [...keys].sort(([a], [b]) => {
      if (a === b) return 0;
      if (a === "unrated") return 1;
      if (b === "unrated") return -1;
      return parseInt(a, 10) - parseInt(b, 10);
    });
  }
  console.log(use, JSON.stringify(sorted), sorted.map(x => (x === "unrated" ? "unrated" : Number(x))));
}
NODE

Repository: kunalverma2512/CodeLens

Length of output: 5055


Handle numeric bucket keys before sorting byRating.

byRating contains non-numeric keys such as "2500plus" and "unrated". parseInt() converts these to NaN, which makes the numeric comparator unreliable for difficulty ordering. Split the mapping/sort/comparison so numeric buckets sort numerically while buckets like "2500plus" and "unrated" are handled consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/ai/service.js` at line 47, Update the byRating sorting logic
to separate numeric bucket keys from non-numeric keys before comparison. Sort
numeric buckets by their parsed numeric values, and handle keys such as
"2500plus" and "unrated" with a consistent explicit ordering rather than
allowing NaN comparisons.

.map(([r, c]) => `${r === "2500plus" ? "2500+" : r}(${c})`)
.join(", ") || "No data yet"
}
Expand Down Expand Up @@ -320,7 +320,7 @@ class AiService {
.lean();

// Attach a short preview of the last message
return conversations.map((conv) => ({
return (conversations ?? []).map((conv) => ({
_id: conv._id,
title: conv.title,
pinned: conv.pinned,
Expand Down Expand Up @@ -436,7 +436,7 @@ class AiService {
// Convert stored messages (excluding the just-added one) to Gemini history format
// We pass all messages EXCEPT the last one (the current user message) as history
const historyMessages = conversation.messages.slice(0, -1);
const geminiHistory = historyMessages.map((msg) => ({
const geminiHistory = (historyMessages ?? []).map((msg) => ({
role: msg.role === "assistant" ? "model" : "user",
parts: [{ text: msg.content }],
}));
Expand Down
2 changes: 1 addition & 1 deletion server/modules/codeforces/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class CodeforcesController {
*/
static async getRecentSubmissions(req, res, next) {
try {
const count = Math.min(parseInt(req.query.count) || 20, 100);
const count = Math.min(parseInt(req.query.count, 10) || 20, 100);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node <<'NODE'
for (const raw of ["-1000000", "10abc", "0", "20"]) {
  const count = Math.min(parseInt(raw, 10) || 20, 100);
  console.log(`${raw} -> ${count}`);
}
NODE

rg -n -C 4 'getRecentSubmissions|\.limit\(count\)' server/modules/codeforces

Repository: kunalverma2512/CodeLens

Length of output: 3675


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf 'Package manifests:\n'
fd -a 'package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$' . | sed 's#^\./##'

printf '\nMongoose versions in manifests/locks:\n'
python3 - <<'PY'
import json, re, pathlib
for p in pathlib.Path('.').rglob('*package-lock.json'):
    try:
        data=json.loads(p.read_text())
    except Exception:
        continue
    for k,v in data.get('packages',{}).items():
        if k.endswith('node_modules/mongoose') or k=='node_modules/mongoose':
            print(p, k, v.get('version'))
for p in pathlib.Path('.').rglob('package.json'):
    try:
        data=json.loads(p.read_text())
    except Exception:
        continue
    deps={}
    for sec in ('dependencies','devDependencies'):
        deps.update(data.get(sec,{}) or {})
    for name,ver in deps.items():
        if name=='mongoose':
            print(p,'mongoose',ver)
PY

printf '\nMongoose package files in repository:\n'
git ls-files | rg 'node_modules/mongoose/(lib/query|query)\.js$|node_modules/mongoose/package\.json$' | sed -n '1,20p' || true

printf '\nGit diff stat:\n'
git diff --stat -- server/modules/codeforces/controller.js server/modules/codeforces/repository.js server/modules/codeforces/service.js

Repository: kunalverma2512/CodeLens

Length of output: 602


🌐 Web query:

Mongoose 9 limit negative value behavior

💡 Result:

In Mongoose, the behavior of the.limit method when passed a negative value is determined by the underlying MongoDB server implementation [1]. A negative limit is treated as a request for a positive number of documents—equal to the absolute value of the negative number provided—with the additional instruction that the cursor should be closed immediately after returning a single batch of results [2][3]. Key aspects of this behavior include: - Cursor Closing: By passing a negative limit, the client tells the server it will not request subsequent batches (i.e., it will not use getMore) [2][3]. - Batch Limitations: Because the cursor closes after one batch, if the result set is large enough that it does not fit into a single batch, the query will return fewer documents than the absolute value of the negative limit specified [2][3][4]. - Undefined Range: The behavior is generally considered undefined for values less than -2^31 and greater than 2^31 [2][3]. Users should generally avoid passing negative values to.limit unless this specific "close-after-batch" behavior is intentionally required, as it can lead to inconsistent result set sizes depending on how data is batched by the MongoDB server [5]. Furthermore, providing extremely large negative numbers may result in a server error indicating that the limit must be non-negative, depending on the specific Mongoose/MongoDB driver version [6].

Citations:


🌐 Web query:

Mongoose Query.prototype.limit number absolute value implementation

💡 Result:

Mongoose's Query.prototype.limit() implementation does not automatically apply an absolute value to the provided number [1][2][3]. In Mongoose, Query.prototype.limit() is designed to specify the maximum number of documents returned by a query [4][5]. Its implementation primarily handles validation and input type casting (ensuring strings are cast to numbers) before assigning the value to the query options [2]. It does not include logic to transform the input into its absolute value [2]. If a negative number is passed to query.limit(), Mongoose will pass that value directly to the underlying MongoDB driver. According to MongoDB documentation, passing a negative value to the limit parameter in some drivers or contexts can be interpreted as a request to close the cursor after returning a single batch, or may result in an error depending on the specific MongoDB command or version being used. Mongoose does not sanitize this by forcing an absolute value [1][2]. If you require the limit to always be positive, you must manually apply Math.abs() to the value before passing it to query.limit(). Example: query.limit(Math.abs(someValue));

Citations:


Reject invalid count values before calling the repository.

Math.min only caps the upper bound, so count=-1000000 reaches CodeforcesRepository.getRecentSubmissions(...).limit(count). The -1000000 value is also greater than parseInt("100abc", 10), so the negative input bypasses the intended cap. Accept only a positive safe integer, otherwise use the default or return a validation error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/codeforces/controller.js` at line 72, Update the count parsing
in the controller before calling CodeforcesRepository.getRecentSubmissions so
only positive safe integers are accepted; use the default count of 20 or return
a validation error for invalid, negative, zero, unsafe, or malformed values,
while preserving the maximum of 100.

const submissions = await CodeforcesService.getRecentSubmissions(
req.user._id,
count
Expand Down
6 changes: 3 additions & 3 deletions server/modules/codeforces/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@
}

// Compute streak
const dates = Object.keys(dailyActivity).sort();
const dates = Object.keys(dailyActivity).sort((a, b) => a - b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node <<'NODE'
const dates = ["2026-08-02", "2026-08-01"];
console.log("numeric:", [...dates].sort((a, b) => a - b));
console.log("lexical:", [...dates].sort());
console.log("subtraction:", dates[0] - dates[1]);
NODE

Repository: kunalverma2512/CodeLens

Length of output: 258


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '--- file context ---\n'
sed -n '1,90p' server/modules/codeforces/service.js

printf '\n--- tests/usages for dailyActivity/longestStreak ---\n'
rg -n "dailyActivity|longestStreak|Object\\.keys\\(dailyActivity\\)" .

Repository: kunalverma2512/CodeLens

Length of output: 252


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- file context ---'
sed -n '1,120p' server/modules/codeforces/service.js

printf '%s\n' ''
printf '%s\n' '--- tests/usages for dailyActivity/longestStreak ---'
rg -n "dailyActivity|longestStreak|Object\.keys\(dailyActivity\)" .

Repository: kunalverma2512/CodeLens

Length of output: 7968


Sort ISO date keys lexicographically.

dailyActivity contains "YYYY-MM-DD" strings, so subtracting them returns NaN and sort() keeps the keys in insertion order. This breaks longestStreak and currentStreak when keys are not already chronological.

Use the default lexicographic sort; ISO dates sort chronologically this way.

Proposed fix
-  const dates = Object.keys(dailyActivity).sort((a, b) => a - b);
+  const dates = Object.keys(dailyActivity).sort();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dates = Object.keys(dailyActivity).sort((a, b) => a - b);
const dates = Object.keys(dailyActivity).sort();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/codeforces/service.js` at line 33, Update the date-key sorting
in the dailyActivity processing to use the default lexicographic sort, removing
the numeric comparator from Object.keys(dailyActivity). This preserves
chronological ordering for YYYY-MM-DD keys and ensures longestStreak and
currentStreak operate correctly.

let currentStreak = 0;
let longestStreak = 0;
let tempStreak = 0;

const today = new Date().toISOString().slice(0, 10);

Check warning on line 38 in server/modules/codeforces/service.js

View workflow job for this annotation

GitHub Actions / Backend Lint & Test

'today' is assigned a value but never used
const sortedDesc = [...dates].reverse();

for (let i = 0; i < sortedDesc.length; i++) {
Expand Down Expand Up @@ -147,7 +147,7 @@
let cfUsers;
try {
cfUsers = await cfGetUserInfo(handle);
} catch (err) {

Check warning on line 150 in server/modules/codeforces/service.js

View workflow job for this annotation

GitHub Actions / Backend Lint & Test

'err' is defined but never used
throw new ApiError(404, `Codeforces handle "${handle}" not found.`);
}

Expand Down Expand Up @@ -274,13 +274,13 @@
// 3. Compute contest participation count from rating history
stats.contestsParticipated = ratingHistory.length;
if (ratingHistory.length > 0) {
const ranks = ratingHistory.map((r) => r.rank).filter(Boolean);
const ranks = (ratingHistory ?? []).map((r) => r.rank).filter(Boolean);
stats.bestRank = ranks.length ? Math.min(...ranks) : null;
stats.worstRank = ranks.length ? Math.max(...ranks) : null;
}

// 4. Persist grouped submission documents
const submissionDocs = submissions.map((sub) => ({
const submissionDocs = (submissions ?? []).map((sub) => ({
user: userId,
submissionId: sub.id,
contestId: sub.contestId,
Expand Down
Loading