Skip to content

Improved Guest Page, Side Bar Layout and readme.md#196

Merged
sumagnadas merged 9 commits intoOpenLake:mainfrom
Arpit-Mahajan09:feature/my-changes
Mar 24, 2026
Merged

Improved Guest Page, Side Bar Layout and readme.md#196
sumagnadas merged 9 commits intoOpenLake:mainfrom
Arpit-Mahajan09:feature/my-changes

Conversation

@Arpit-Mahajan09
Copy link
Copy Markdown
Contributor

@Arpit-Mahajan09 Arpit-Mahajan09 commented Mar 13, 2026

Pull Request Description
Title: feat: Enhance streak system, sidebar transitions, user data handling & guest mode

Summary
This PR includes a set of improvements and fixes across the frontend and backend of the Leaderboard Pro application.

Changes
-Streak System
Enhanced streak calculation logic across all platforms (Codeforces, LeetCode, GitHub, AtCoder, CodeChef)
Added PlatformStreakFetcher component for modular platform-specific streak fetching
Fixed timezone and date-handling issues that caused streaks to incorrectly display as 0
Navbar now displays the global highest streak across all integrated platforms

-UI / Frontend
Improved sidebar transitions and layout (consolidated navigation groups, better spacing)
Navbar updated with streak display and improved toggle behavior
Fixed scaling/overflow issues on main content area
LandingPage layout and styling improvements
MobileMenu refined for better responsiveness

  • User & Auth
    Improved user data handling and state management in App.jsx
    Hardened guest mode layout — fixed crash on guest homepage
    Improved login/register flows

  • Backend
    Updated views.py with improved API handling
    Minor fixes to models.py and users.py

  • Dependencies
    Updated package.json and pnpm-lock.yaml with latest dependency versions
    Testing
    Streaks verified for all platforms
    Guest mode and authenticated flows tested
    Sidebar and navbar interactions tested on desktop and mobile viewports
    You can paste this directly into the GitHub PR description at: 👉 https://github.com/Arpit-Mahajan09/Leaderboard-Pro/pull/new/feature/my-changes

Summary by CodeRabbit

  • New Features

    • Added AtCoder leaderboard rankings and user integration.
    • Introduced achievements system allowing users to unlock and track accomplishments.
    • Enabled voting (like/dislike) on blog posts and discussions.
    • Launched multi-platform friend management with dedicated UI.
    • Added collapsible sidebar for improved responsive navigation.
  • Improvements

    • Optimized leaderboard data fetching for better performance.
    • Enhanced authentication gating for user-specific features.
    • Improved responsive design across multiple components.

@github-actions

This comment has been minimized.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces a comprehensive feature expansion with AtCoder platform support, an achievement/badge system, discussion post voting, backend-backed friends management, and a significant sidebar/navigation UI redesign. It includes new Django models, migrations, API endpoints, Celery tasks, and refactored frontend components with responsive styling updates.

Changes

Cohort / File(s) Summary
AtCoder Platform Integration
api/leaderboard/models.py, api/leaderboard/serializers.py, api/leaderboard/views.py, api/leaderboard/friends.py, api/leaderboard/urls.py, api/leaderboard/settings.py, api/leaderboard/management/commands/update_db.py, api/leaderboard/celery.py, api/leaderboard/migrations/0012_..., api/leaderboard/api/views.py
Added AtcoderUser foreign key lookup, new friend management endpoints (addition/deletion/list), Celery scraping task with multi-table parsing, scheduled updates via beat schedule, and migration infrastructure.
Achievement System
api/leaderboard/models.py, api/leaderboard/serializers.py, api/leaderboard/views.py, api/leaderboard/urls.py, api/leaderboard/migrations/0013_achievement.py, app/src/components/Achievements.jsx
Added Achievement model with unique constraints, API endpoints for viewing and unlocking achievements, serializer, and refactored frontend component with simplified stats fetching via userDetails endpoint.
Blog/Discussion Voting System
api/leaderboard/models.py, api/leaderboard/serializers.py, api/leaderboard/views.py, api/leaderboard/migrations/0014_postvote.py, app/src/components/Blogs.jsx
Added PostVote model for tracking votes (like/dislike), updated DiscussionPostManage to annotate user_vote, support toggle/swap voting logic with optimistic updates, and added delete capability.
Sidebar & Navigation UI Refactor
app/src/App.jsx, app/src/components/Navbar.jsx, app/src/components/NavMenu.jsx, app/src/components/GoToTop.jsx, app/src/components/ui/sidebar.jsx, app/src/App.css, app/src/index.css
Redesigned sidebar to be collapsible with icon mode, reworked navigation with active state indicators and transitions, replaced SidebarTrigger-based layout with SidebarInset wrapper, and improved global overflow handling.
Backend Friends Management
api/leaderboard/views.py, api/leaderboard/friends.py
Converted friends lists to backend-backed operations with dedicated POST endpoints for add/remove, updated DiscussionReplyManage and DiscussionPostManage to use request.user instead of client-provided usernames, and enforced ownership checks.
Frontend Friend Management & Blogs
app/src/components/Friends.jsx, app/src/components/CodeforcesTable.jsx, app/src/components/Blogs.jsx
Added new multi-platform Friends component with per-platform state and search, refactored CodeforcesTable to fetch/manage friends from backend, and expanded Blogs component with voting UI, deletion, and Authorization headers.
Responsive Design & Table Cleanup
app/src/components/AtcoderTable.jsx, app/src/components/CodechefTable.jsx, app/src/components/CodeforcesTable.jsx, app/src/components/GithubTable.jsx, app/src/components/Login.jsx, app/src/components/Register.jsx, app/src/components/Profile.jsx
Removed dynamic width calculations tied to sidebar state (open/isMobile), replacing with static class-based sizing to simplify responsive behavior.
Homepage & Streak Updates
app/src/components/HomePage.jsx, app/src/components/PlatformStreakFetcher.jsx, app/src/components/Heatmap.jsx, app/src/Context/AuthContext.jsx
Refactored Cards to accept destructured usernames object, added authentication gate with new landing UI (hero card, feature cards), extended streak calculation to CodeChef and AtCoder, and added ac_uname to profile form schema.
Package & Environment Configuration
app/package.json, app/.env.frontend, api/leaderboard/migrations/0015_usernames_ac_uname.py
Added Radix UI dependencies (@radix-ui/react-progress, radix-ui, date-fns), cleared frontend .env.frontend file, and added ac_uname CharField to UserNames model via migration.
Database & API Root
api/leaderboard/root.py, README.md
Added URL reverses for AtCoder friend endpoints to API root response, and updated README with installation instructions (make/pnpm setup), Docker Compose guidance, and new Leaderboard Type 7 (AtCoder rankings).

Sequence Diagrams

sequenceDiagram
    participant Client as Client Browser
    participant Server as Django Server
    participant Database as Database
    
    Client->>Server: POST /achievements/unlock/<br/>(slug, tier)
    activate Server
    Server->>Database: get_or_create Achievement<br/>(user, slug, tier)
    activate Database
    Database-->>Server: Achievement object
    deactivate Database
    Server-->>Client: 201 Created or<br/>200 Already unlocked
    deactivate Server
Loading
sequenceDiagram
    participant Client as Client Browser
    participant Server as Django Server
    participant Database as Database
    
    Client->>Server: PUT /blogs/<br/>(title, action: like/dislike)
    activate Server
    Server->>Database: Get PostVote(user, post)
    activate Database
    Database-->>Server: PostVote or None
    deactivate Database
    
    alt Vote exists & same action
        Server->>Database: Delete PostVote
        Server->>Database: Decrement post.likes/dislikes
    else Vote exists & different action
        Server->>Database: Update vote_type (toggle)
        Server->>Database: Increment one counter,<br/>decrement other
    else No vote exists
        Server->>Database: Create PostVote
        Server->>Database: Increment post.likes/dislikes
    end
    
    Server-->>Client: 200 Updated blog payload
    deactivate Server
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • sumagnadas

Poem

🐰 A leaderboard grows with a new friend atop,
Achievements unlock as the votes never stop,
From CodeForces to AtCoder's grand hall,
The sidebar collapses—and then shows it all! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description lacks required template sections and is significantly incomplete compared to the specified template requirements for this repository. Add missing template sections: Related Issue(s), Screenshots, Type of Change checkboxes, and complete the Checklist. Provide specific issue references and verify all items are addressed.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately captures the main focus areas of the changeset: guest page improvements, sidebar layout enhancements, and readme documentation updates. It aligns with the actual changes in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
app/src/components/Heatmap.jsx (1)

142-188: ⚠️ Potential issue | 🟠 Major

Missing calculateStreak call in processGitHubData.

processCodeforcesData (line 139) calls calculateStreak(heatmapArray) after setting the heatmap data, but processGitHubData does not. This means GitHub contributions won't update the streak display.

🐛 Proposed fix
    console.log("Processed heatmap data:", heatmapArray); // Debug log
    setHeatmapData(heatmapArray);
+   calculateStreak(heatmapArray);
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Heatmap.jsx` around lines 142 - 188, processGitHubData
builds heatmapArray but never updates the streak like processCodeforcesData
does; after setHeatmapData(heatmapArray) call calculateStreak(heatmapArray) to
compute and update the streak display (ensure you call the same calculateStreak
function used by processCodeforcesData so GitHub contributions update the streak
UI).
app/src/components/CodechefTable.jsx (1)

70-78: ⚠️ Potential issue | 🟠 Major

Bug: Profile link points to wrong domain.

The CodeChef table links usernames to codeforces.com instead of codechef.com. Users will be redirected to non-existent or incorrect profiles.

🐛 Proposed fix
           <Button variant="link" asChild>
             <a
-              href={`https://codeforces.com/profile/${username}`}
+              href={`https://www.codechef.com/users/${username}`}
               target="_blank"
               rel="noreferrer"
             >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodechefTable.jsx` around lines 70 - 78, The profile
anchor in the CodechefTable.jsx component currently builds URLs using the wrong
domain (https://codeforces.com/profile/${username}); update the href to use the
CodeChef domain (https://www.codechef.com/users/${username}) so usernames link
to the correct profiles—locate the anchor inside the Button (variant="link"
asChild) in the CodechefTable component and change the URL template accordingly,
keeping target="_blank" and rel="noreferrer" behavior intact.
api/leaderboard/views.py (1)

125-146: ⚠️ Potential issue | 🟠 Major

Race condition: TOCTOU vulnerability in user creation.

The check-then-create pattern (exists() followed by save()) has a race condition. Two concurrent POST requests for the same username can both pass the existence check, then one will fail with IntegrityError when the second save() violates the unique constraint.

Use get_or_create or update_or_create for atomic operation:

🐛 Proposed fix using get_or_create
-        # check if user already exists
-        if githubUser.objects.filter(username=username).exists():
-             return Response(
-                {"message": "User already exists"}, status=status.HTTP_200_OK
-            )
-
         # Create user and fetch GitHub data
         github_data = self.fetch_github_data(username)
         if not github_data:
             return Response(
                 {"error": "GitHub user not found"}, status=status.HTTP_404_NOT_FOUND
             )

-        gh_user = githubUser(
-            username=username,
+        gh_user, created = githubUser.objects.update_or_create(
+            username=username,
+            defaults={
+                "avatar": github_data["avatar"],
+                "repositories": github_data["repositories"],
+                "stars": github_data["stars"],
+                "contributions": github_data["contributions"],
+            }
+        )
-            avatar=github_data["avatar"],
-            repositories=github_data["repositories"],
-            stars=github_data["stars"],
-            contributions=github_data["contributions"],
-            last_updated=github_data["last_updated"],
-        )
-        gh_user.save()

-        return Response(GH_Serializer(gh_user).data, status=status.HTTP_201_CREATED)
+        status_code = status.HTTP_201_CREATED if created else status.HTTP_200_OK
+        return Response(GH_Serializer(gh_user).data, status=status_code)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 125 - 146, The current
check-then-create in the view (githubUser.exists() followed by creating
githubUser and gh_user.save()) is vulnerable to a TOCTOU race; replace this
pattern with an atomic operation such as githubUser.objects.get_or_create(...)
or githubUser.objects.update_or_create(...) when creating the record after
calling fetch_github_data(username), and handle the returned (instance, created)
tuple appropriately (or catch IntegrityError around save() as a fallback).
Ensure you populate fields (avatar, repositories, stars, contributions,
last_updated) in the get_or_create/update_or_create call or update the instance
when created==False to avoid duplicate creation errors.
app/src/components/CodeforcesTable.jsx (1)

96-99: ⚠️ Potential issue | 🟡 Minor

Replace { timeout: 10000 } with AbortController to implement request timeouts.

The native fetch API does not support the timeout option and will ignore it. Requests will wait indefinitely (or until the browser's default timeout). Use AbortController with AbortSignal to implement timeouts.

Example fix using AbortController
const fetchWithTimeout = async (url, timeoutMs = 10000) => {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    throw error;
  }
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` around lines 96 - 99, Replace the
unsupported { timeout: 10000 } option in the fetch call that creates cfResponse
with an AbortController-based timeout; create a helper like fetchWithTimeout (or
inline controller logic) that constructs an AbortController, sets a setTimeout
to controller.abort() after the desired ms, calls fetch(url, { signal:
controller.signal }), clears the timeout on success or failure, and rethrows
errors so callers of the function in CodeforcesTable.jsx can handle
AbortError/timeouts appropriately.
🧹 Nitpick comments (26)
app/src/components/GoToTop.jsx (1)

9-28: Unnecessary React Fragment.

When returning a single element, the Fragment wrapper is redundant. You can return the button directly.

♻️ Suggested simplification
  return (
-    <>
      <button
        className="bg-foreground text-background"
        style={{
          position: "fixed",
          bottom: "3rem",
          right: "3rem",
          zIndex: "999",
          display: "flex",
          justifyContent: "center",
          width: "3rem",
          height: "3rem",
          alignItems: "center",
          borderRadius: "50%",
        }}
        onClick={gotop}
      >
        <ArrowUp />
      </button>
-    </>
  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/GoToTop.jsx` around lines 9 - 28, The JSX in GoToTop.jsx
wraps a single button element in an unnecessary React Fragment; remove the
surrounding <>...</> and return the <button> directly from the component (keep
the existing props, inline styles, onClick handler gotop, and child ArrowUp
unchanged) so the component renders the button without the redundant fragment.
app/src/components/GithubTable.jsx (3)

197-199: Misleading variable name fruit in filter callback.

This appears to be a copy-paste artifact. Rename to something meaningful like friend or username.

      setGithubfriends((current) =>
-        current.filter((fruit) => fruit !== e),
+        current.filter((friend) => friend !== e),
      );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/GithubTable.jsx` around lines 197 - 199, The filter
callback in the setGithubfriends update uses a misleading name "fruit"; update
the callback parameter to a meaningful identifier (e.g., "friend" or "username")
so the code reads setGithubfriends((current) => current.filter((friend) =>
friend !== e)); locate this inside the component where setGithubfriends is
called and replace "fruit" with the chosen identifier in that callback only.

205-212: Missing getghfriends in useEffect dependency array.

The getghfriends function is called inside this effect but not listed in the dependency array. This can lead to stale closures. Either add it to the deps array (after wrapping with useCallback) or move the function logic inline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/GithubTable.jsx` around lines 205 - 212, The effect calls
getghfriends but doesn’t include it in the dependency array, which can cause
stale closures; either wrap getghfriends in useCallback (e.g., memoize the
getghfriends function so it only changes when its real dependencies change) and
then add getghfriends to the useEffect deps, or move the getghfriends logic
inline inside the useEffect and keep the dependency array as [isAuthenticated];
ensure setGithubfriends and setGHshowfriends remain available from React state
and update only within the effect or the memoized function.

46-46: Unused destructured variables from useSidebar().

open and isMobile are destructured but no longer used after the layout simplification. Consider removing them to avoid confusion.

-  const { open, isMobile } = useSidebar();
+  const { } = useSidebar();

Or remove the import entirely if no sidebar context is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/GithubTable.jsx` at line 46, Remove the unused
destructured variables from useSidebar() in GithubTable.jsx: delete "open" and
"isMobile" from the const { open, isMobile } = useSidebar() call (or remove the
useSidebar() call/import entirely if no sidebar context is needed anywhere in
this component) so the component no longer declares unused variables and the
import is cleaned up accordingly.
app/src/components/AtcoderTable.jsx (3)

16-16: Unused destructured variables from useSidebar().

open and isMobile are not used. Consider removing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/AtcoderTable.jsx` at line 16, The destructured variables
open and isMobile from useSidebar() in AtcoderTable.jsx are unused; remove them
by changing the destructuring to only grab the needed value(s) (e.g., const { /*
neededProp */ } = useSidebar() or simply const sidebar = useSidebar() if you
need the whole object) or call useSidebar() without destructuring if nothing is
needed; update any references to open or isMobile (none expected) and run lint
to ensure no remaining unused variable warnings.

6-11: Unused imports: Avatar, AvatarFallback, AvatarImage, User.

These are imported but not used in this component's column definitions.

-import {
-  Avatar,
-  AvatarFallback,
-  AvatarImage,
-} from "@/components/ui/avatar";
-import { User } from "lucide-react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/AtcoderTable.jsx` around lines 6 - 11, The file imports
Avatar, AvatarFallback, AvatarImage and User but they are not referenced in the
AtcoderTable component's column definitions; remove these unused imports
(Avatar, AvatarFallback, AvatarImage, User) from the top of AtcoderTable.jsx or
replace the import with only symbols actually used by the component so the
linter warning goes away and dead imports are eliminated.

102-102: Avoid full page reload after adding user.

window.location.reload() provides poor UX. Consider lifting state up or using a callback prop to trigger a data refetch instead.

-        window.location.reload(); 
+        // TODO: Trigger parent refresh via callback prop
+        // onUserAdded?.();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/AtcoderTable.jsx` at line 102, AtcoderTable currently
forces a full page reload via window.location.reload() after adding a user;
remove that call and instead invoke a passed-in callback or trigger a local
state update to refresh the list. Update the add-user handler (e.g., the
function containing window.location.reload()) to call a prop like onUserAdded or
call a local fetchUsers/refreshData method after the add completes, or lift the
users state up and update it from the parent so the UI re-renders without a
reload. Ensure the component accepts the callback prop
(onUserAdded/refreshUsers) and uses it in place of window.location.reload() so
new data is fetched/reconciled gracefully.
app/src/components/Register.jsx (1)

43-43: Unused destructured variables from useSidebar().

open and isMobile are destructured but no longer used after removing dynamic width styling.

-  const { open, isMobile } = useSidebar();

Consider removing this line and the import if sidebar context is not needed elsewhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Register.jsx` at line 43, The destructured variables open
and isMobile from useSidebar() in Register.jsx are unused; remove the
unnecessary destructuring (and the useSidebar import if it becomes unused) to
eliminate dead code. Locate the line "const { open, isMobile } = useSidebar();"
and replace it with either a no-op call to useSidebar() if side effects are
needed or remove the call entirely, and then remove the corresponding import of
useSidebar from the top of the file if nothing else uses it.
app/src/components/LeetcodeTable.jsx (3)

216-223: Missing getltfriends in useEffect dependency array.

Similar to GithubTable, the async function getltfriends is called but not in deps, which may cause stale closure issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/LeetcodeTable.jsx` around lines 216 - 223, The useEffect
in LeetcodeTable.jsx references the async function getltfriends but doesn't
include it in the dependency array, risking stale closures; either add
getltfriends to the dependency array of the useEffect([isAuthenticated,
getltfriends]) or memoize getltfriends with useCallback (so its identity is
stable) and then include that memoized function in the deps; keep existing calls
to setLeetcodefriends and setLTshowfriends and the isAuthenticated check
unchanged.

46-46: Unused destructured variables from useSidebar().

open and isMobile are no longer used after the layout change. Consider removing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/LeetcodeTable.jsx` at line 46, The destructured variables
open and isMobile from useSidebar() are unused; update the call in
LeetcodeTable.jsx by removing those identifiers so you only extract what’s
needed (e.g., const { /*keep only used props*/ } = useSidebar() or simply call
useSidebar() without destructuring), and ensure there are no remaining
references to open or isMobile in functions or JSX (check any usages in render,
handlers, or effects that might need refactoring or safe removal).

208-210: Misleading variable name fruit in filter callback.

Same issue as in GithubTable. Rename to friend or username for clarity.

      setLeetcodefriends((current) =>
-        current.filter((fruit) => fruit !== e),
+        current.filter((friend) => friend !== e),
      );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/LeetcodeTable.jsx` around lines 208 - 210, The filter
callback inside setLeetcodefriends uses a misleading parameter name `fruit`;
change it to a clearer name like `friend` or `username` and update any
references in the callback (e.g., the equality check against `e`) so the code
reads setLeetcodefriends((current) => current.filter((friend) => friend !== e));
this makes the intent in LeetcodeTable.jsx and the setLeetcodefriends state
updater clearer and consistent with GithubTable.
app/src/components/Login.jsx (1)

31-31: Unused destructured variables from useSidebar().

open and isMobile are no longer used. Consider removing the destructuring and potentially the import.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Login.jsx` at line 31, The destructured variables open and
isMobile from useSidebar() in the Login component are unused; remove them from
the destructuring (replace "const { open, isMobile } = useSidebar();" with a
call that only uses needed values or simply "useSidebar();" if no values are
needed) and remove the useSidebar import if nothing from it is used elsewhere in
Login.jsx; ensure you only keep the specific hook usage required by the
component (e.g., keep useSidebar() or specific properties like isOpen if
actually used).
app/src/components/Heatmap.jsx (2)

21-33: Missing dependencies in useEffect.

The effect references fetchGitHubContributions, fetchCodeforcesData, and generateMockData but doesn't include them in the dependency array. This can cause stale closures. Consider using useCallback for these functions or restructuring to avoid the warning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Heatmap.jsx` around lines 21 - 33, The useEffect in
Heatmap.jsx references fetchGitHubContributions, fetchCodeforcesData, and
generateMockData but doesn't list them in the dependency array, causing stale
closures; fix by either wrapping those functions in useCallback (e.g., const
fetchGitHubContributions = useCallback(..., [itsDeps]), const
fetchCodeforcesData = useCallback(..., [itsDeps]), const generateMockData =
useCallback(..., [itsDeps])) and then adding them to the useEffect dependency
array, or move their implementations directly inside the useEffect so the effect
only depends on platform and username; ensure any internal state or props those
functions use are included in their useCallback dependency arrays.

227-239: Remove commented-out debug logs.

These commented console.log statements are debug artifacts that should be removed rather than left in the codebase.

      if (level > 0) {
        streak++;
        newStreakCells.add(i);
-        // console.log(`Streak incremented at index ${i}, level: ${level}, streak: ${streak}`);
      } else {
        if (i === flatData.length - 1) {
-             // Skip today if no activity yet
-             // console.log(`Today (index ${i}) has no activity, continuing streak check...`);
             continue;
        } else {
-           // console.log(`Streak broken at index ${i} (yesterday or older), level: ${level}`);
           break;
        }
      }
    }
-    // console.log(`Final calculated streak for ${platform}: ${streak}`);
    setCurrentStreak(streak);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Heatmap.jsx` around lines 227 - 239, In the Heatmap
component's streak-calculation loop (the code referencing flatData, index i,
level and streak), remove all commented-out console.log debug lines and any
adjacent commented debug messages (e.g., "// console.log(`Streak
incremented...`)", "// console.log(`Today (index ${i})...`)", "//
console.log(`Streak broken...`)", "// console.log(`Final calculated
streak...`)") so the loop contains only active logic and necessary comments.
app/src/App.css (1)

6-16: Replace height: 100vh with min-height: 100dvh for better mobile viewport handling.

Setting height: 100vh on .App causes issues on mobile browsers where 100vh doesn't account for the dynamic address bar. Use min-height: 100dvh instead—the dynamic viewport height unit is now broadly supported (94%+ of browsers as of 2026) and designed specifically for this problem.

Additionally, overflow-x: hidden on html, body can hide overflowing content rather than addressing underlying layout issues. Ensure this is intentional.

Suggested fix
 .App {
   overflow-x: hidden;
   max-width: 100%;
-  height: 100vh;
+  min-height: 100dvh;
   overflow-y: auto;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/App.css` around lines 6 - 16, Replace the hard height on the root app
container and reconsider global overflow: for the .App selector change the fixed
"height: 100vh" to "min-height: 100dvh" to use the dynamic viewport height on
mobile (keep existing overflow-y: auto); review the global "html, body {
overflow-x: hidden; max-width: 100vw; }" usage and remove or narrow it if it was
masking layout/overflow bugs (or add a comment explaining it's intentional) so
the root/container selectors reflect the intended behavior.
app/src/components/Profile.jsx (1)

35-35: Remove unused useSidebar hook call.

The destructured values open and isMobile are no longer used after removing the dynamic width calculation. Remove the unused hook call to avoid unnecessary re-renders and improve code clarity.

♻️ Proposed fix
-import { useSidebar } from "@/components/ui/sidebar";
...
-  const { open, isMobile } = useSidebar();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Profile.jsx` at line 35, Remove the unused useSidebar hook
call in Profile.jsx: the destructured values open and isMobile are no longer
referenced, so delete the line "const { open, isMobile } = useSidebar();" (and
the import of useSidebar if it becomes unused) to prevent unnecessary re-renders
and clean up the component. Ensure no other references to open or isMobile
remain before removing the import.
api/leaderboard/views.py (1)

112-113: Prefer catching specific exceptions.

Catching broad Exception can mask unexpected errors. Consider catching specific exceptions like requests.RequestException for network errors.

♻️ Proposed fix
-            except Exception as e:
-                logger.error(f"Failed to fetch GitHub data for {user.username}: {e}")
+            except requests.RequestException as e:
+                logger.error(f"Failed to fetch GitHub data for {user.username}: {e}")
+            except (KeyError, TypeError) as e:
+                logger.error(f"Invalid GitHub data format for {user.username}: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 112 - 113, The current except block
catches a broad Exception for the GitHub fetch and should be narrowed: replace
"except Exception as e" with specific network/HTTP exceptions such as "except
requests.RequestException as e" (optionally also handle requests.HTTPError,
requests.Timeout, requests.ConnectionError separately if you want different
messages), ensure the requests module is imported, keep the existing
logger.error(f"Failed to fetch GitHub data for {user.username}: {e}") inside
that specific except, and allow other unexpected exceptions to propagate (or
re-raise) so they aren't silently swallowed.
app/src/components/CodeforcesTable.jsx (2)

62-62: Remove debug console.log statements.

Multiple console.log statements (lines 62, 80, 89, 95, 128, 140, 149) should be removed before merging to avoid polluting browser console in production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` at line 62, Remove all development
console.log calls in the CodeforcesTable component (e.g., the logs at lines 62,
80, 89, 95, 128, 140, 149) so they don't leak to production; locate the prints
inside the CodeforcesTable component (within its useEffect hooks and any helper
functions that fetch or process userData) and either delete the console.log
statements or wrap them behind a NODE_ENV check (e.g., only call console.debug
when process.env.NODE_ENV !== 'production') or replace with a proper logger if
available.

26-26: Remove unused useSidebar hook call.

The destructured values open and isMobile are no longer used after removing the dynamic width calculation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` at line 26, Remove the unused
useSidebar hook invocation in CodeforcesTable.jsx: delete the line "const {
open, isMobile } = useSidebar();" and any related destructuring, and also remove
the now-unused useSidebar import (and its import specifier) from the top of the
file to avoid unused-import warnings.
app/src/components/CodechefTable.jsx (1)

36-36: Remove unused useSidebar hook call.

The destructured values open and isMobile are no longer used after removing the dynamic width calculation.

♻️ Proposed fix
-import { useSidebar } from "@/components/ui/sidebar";
...
-  const { open, isMobile } = useSidebar();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodechefTable.jsx` at line 36, In the CodechefTable
component remove the unused useSidebar hook call and its destructuring (the line
"const { open, isMobile } = useSidebar();") and also remove the corresponding
import of useSidebar; this cleans up unused variables in CodechefTable.jsx and
prevents importing a hook that is no longer referenced.
app/src/components/Navbar.jsx (1)

212-212: Simplify redundant nullish coalescing.

(user ?? false) is equivalent to just user in a boolean/ternary context since falsy values already evaluate correctly.

✨ Proposed simplification
-          {(user ?? false) ? (
+          {user ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Navbar.jsx` at line 212, The JSX conditional uses a
redundant nullish coalescing expression "(user ?? false)" — replace it with the
simpler truthy check "user" wherever it appears in the Navbar component's JSX
conditional rendering so the ternary or logical rendering uses "user" directly
(look for the JSX expression using (user ?? false) in Navbar.jsx and update it
to user).
app/src/components/NavMenu.jsx (2)

75-75: Redundant condition: globalStreak >= 0 is always true.

Since globalStreak is computed with Math.max(..., 0), it will always be >= 0. The user && check is sufficient.

✨ Proposed simplification
-            {user && globalStreak >= 0 && (
+            {user && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/NavMenu.jsx` at line 75, Remove the redundant
`globalStreak >= 0` check in the NavMenu JSX conditional — since `globalStreak`
is already computed with `Math.max(..., 0)` it will always be >= 0; update the
conditional expression `{user && globalStreak >= 0 && (...)}` to simply `{user
&& (...)}` referencing the `globalStreak` variable and the JSX block in
NavMenu.jsx so the UI logic relies only on `user` for rendering.

62-69: Search input appears non-functional.

The search input is rendered but has no onChange handler or associated search logic. Consider either implementing search functionality or removing the placeholder input to avoid user confusion.

Would you like me to help implement a basic search functionality, or should this be tracked as a TODO for a future PR?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/NavMenu.jsx` around lines 62 - 69, The Search input in
NavMenu.jsx is rendered but not wired up; add a controlled state (e.g., const
[searchQuery, setSearchQuery] = useState('')) and an onChange handler (e.g.,
function handleSearchChange(e) { setSearchQuery(e.target.value); }) and pass it
to the Input as onChange={handleSearchChange} and value={searchQuery}; then
either call a search function from that handler (or a debounced wrapper) such as
fetchSearchResults(searchQuery) or invoke a prop callback (e.g.,
props.onSearch(searchQuery)) to perform filtering/fetching, and remove the input
entirely if you prefer to postpone implementing search. Ensure you reference the
Input component and the NavMenu.jsx component's user conditional when making the
change.
app/package.json (1)

44-44: Consider removing redundant radix-ui meta-package.

You're already using individual @radix-ui/* packages (lines 13-23). The radix-ui meta-package bundles all Radix components, which increases bundle size unnecessarily. If you only need specific components, the individual packages you already have are sufficient.

🗑️ Proposed fix
-    "radix-ui": "^1.4.3",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/package.json` at line 44, The package.json currently includes the
redundant meta-package "radix-ui" which duplicates the already listed individual
`@radix-ui/`* packages and increases bundle size; remove the "radix-ui" dependency
entry from package.json (the line with "radix-ui": "^1.4.3") so only the
specific `@radix-ui/`* packages (those listed earlier) remain, then run your
package manager install to update lockfile and verify builds/tests pass.
app/src/components/PlatformStreakFetcher.jsx (1)

40-64: Consider extracting shared streak calculation logic.

The streak calculation algorithm is nearly identical across fetchLeetcodeStreak, fetchAtcoderStreak, and fetchCodechefStreak. Extracting a shared helper would reduce duplication and ensure consistent behavior.

♻️ Example helper extraction
// Helper to calculate streak from sorted descending day timestamps
function calculateStreakFromDays(sortedDays, now) {
  let streak = 0;
  for (let i = 0; i < sortedDays.length; i++) {
    const dayTime = sortedDays[i];
    const dayDiff = Math.floor((now.getTime() - dayTime) / (1000 * 60 * 60 * 24));
    
    if (streak === 0) {
      if (dayDiff <= 1) {
        streak = 1;
      } else {
        break;
      }
    } else {
      const prevTime = sortedDays[i - 1];
      const roundedDiff = Math.round((prevTime - dayTime) / (1000 * 60 * 60 * 24));
      
      if (roundedDiff === 1) {
        streak++;
      } else if (roundedDiff === 0) {
        continue;
      } else {
        break;
      }
    }
  }
  return streak;
}

Also applies to: 100-123, 154-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/PlatformStreakFetcher.jsx` around lines 40 - 64, Extract
the duplicated streak loop into a shared helper (e.g., calculateStreakFromDays)
and replace the logic inside fetchLeetcodeStreak, fetchAtcoderStreak, and
fetchCodechefStreak with calls to that helper; the helper should accept an array
of sorted descending day timestamps (or numeric day keys converted to ms) and a
Date/now value, implement the same logic currently in the loop (initial day
check using dayDiff <= 1, skipping same-day entries, incrementing on roundedDiff
=== 1, breaking otherwise) and return the computed streak number so each fetch*
function simply prepares the sorted timestamps and returns
calculateStreakFromDays(sortedDays, now).
app/src/components/HomePage.jsx (1)

37-37: Use const instead of var for module-level constant.

rank is never reassigned, so use const. If this value should be dynamic, consider moving it into component state or computing it from props.

✨ Proposed fix
-var rank = 0;
+const rank = 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/HomePage.jsx` at line 37, The module-level variable "rank"
is declared with var but never reassigned; update its declaration to use const
(replace the "var rank = 0;" declaration with "const rank = 0;") in
HomePage.jsx, or if rank is meant to change, move it into component state (e.g.,
useState inside the relevant component) or compute it from props instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/components/Navbar.jsx`:
- Line 189: The Link in Navbar.jsx uses a relative path via to={linkLower};
update it to use an absolute route by prepending a slash (i.e., set the Link's
to prop to `/${linkLower}`) so it matches the isActive check pattern used
earlier (see the other use of `/${linkLower}`) and prevents relative routing
issues; locate the Link element that references linkLower and change its to
value accordingly.
- Around line 118-127: The off-by-one bug prevents the Blogs spacing because
isBlogs is set with index === 7 but Blogs is at index 6; update the condition in
the map (where isActive and isBlogs are computed for items.map) to correctly
identify Blogs — either change index === 7 to index === 6 or, more robustly,
check the item identity (e.g., item.title === "Blogs" or item.url === "/blogs")
before passing the className to SidebarMenuItem so the "mt-8" spacing is applied
to the Blogs entry.

In `@app/src/components/PlatformStreakFetcher.jsx`:
- Around line 134-190: The fetchCodechefStreak function assumes data.heatMap
exists (so streak logic never runs); fix by either (A) switching the fetch URL
to an endpoint that returns submission/activity dates and update
fetchCodechefStreak to parse that response (e.g., request an
activity/submissions API that returns dates and build the sortedEntries from
that), or (B) change the backend API to include a heatMap/activity array for the
existing endpoint so fetchCodechefStreak can use it; in either case remove the
data.heatMap guard and instead parse the actual activity field returned (or
return early with updateStreak(platform, 0) only when that activity array is
absent/empty), keeping function name fetchCodechefStreak and final
updateStreak(platform, streak) usage. Ensure error handling stays and add a
small comment in fetchCodechefStreak indicating which field it expects from the
API.

In `@app/src/index.css`:
- Around line 5-14: The css sets html, body { overflow: hidden } which prevents
window.scrollTo in GoToTop.jsx from working because the scrollable container is
.App (overflow-y: auto, height: 100vh); fix by either removing overflow: hidden
from the html, body rule in index.css so window scrolling works, or modify
GoToTop.jsx to target the .App element (query/select the element with class
"App" and call its scrollTo or set scrollTop with smooth behavior) instead of
calling window.scrollTo; update references to window.scrollTo({ left:0, top:0,
behavior:"smooth" }) to use the .App element's scrolling API when choosing that
approach.

In `@README.md`:
- Line 18: Fix the typo and improve readability in the README sentence by
changing "there achievements" to "their achievements" and replacing "on the
basis of" with "based on" so the line reads e.g. "Students can also login and
view their achievements based on previous accomplishments"; update the exact
sentence in README.md accordingly.

---

Outside diff comments:
In `@api/leaderboard/views.py`:
- Around line 125-146: The current check-then-create in the view
(githubUser.exists() followed by creating githubUser and gh_user.save()) is
vulnerable to a TOCTOU race; replace this pattern with an atomic operation such
as githubUser.objects.get_or_create(...) or
githubUser.objects.update_or_create(...) when creating the record after calling
fetch_github_data(username), and handle the returned (instance, created) tuple
appropriately (or catch IntegrityError around save() as a fallback). Ensure you
populate fields (avatar, repositories, stars, contributions, last_updated) in
the get_or_create/update_or_create call or update the instance when
created==False to avoid duplicate creation errors.

In `@app/src/components/CodechefTable.jsx`:
- Around line 70-78: The profile anchor in the CodechefTable.jsx component
currently builds URLs using the wrong domain
(https://codeforces.com/profile/${username}); update the href to use the
CodeChef domain (https://www.codechef.com/users/${username}) so usernames link
to the correct profiles—locate the anchor inside the Button (variant="link"
asChild) in the CodechefTable component and change the URL template accordingly,
keeping target="_blank" and rel="noreferrer" behavior intact.

In `@app/src/components/CodeforcesTable.jsx`:
- Around line 96-99: Replace the unsupported { timeout: 10000 } option in the
fetch call that creates cfResponse with an AbortController-based timeout; create
a helper like fetchWithTimeout (or inline controller logic) that constructs an
AbortController, sets a setTimeout to controller.abort() after the desired ms,
calls fetch(url, { signal: controller.signal }), clears the timeout on success
or failure, and rethrows errors so callers of the function in
CodeforcesTable.jsx can handle AbortError/timeouts appropriately.

In `@app/src/components/Heatmap.jsx`:
- Around line 142-188: processGitHubData builds heatmapArray but never updates
the streak like processCodeforcesData does; after setHeatmapData(heatmapArray)
call calculateStreak(heatmapArray) to compute and update the streak display
(ensure you call the same calculateStreak function used by processCodeforcesData
so GitHub contributions update the streak UI).

---

Nitpick comments:
In `@api/leaderboard/views.py`:
- Around line 112-113: The current except block catches a broad Exception for
the GitHub fetch and should be narrowed: replace "except Exception as e" with
specific network/HTTP exceptions such as "except requests.RequestException as e"
(optionally also handle requests.HTTPError, requests.Timeout,
requests.ConnectionError separately if you want different messages), ensure the
requests module is imported, keep the existing logger.error(f"Failed to fetch
GitHub data for {user.username}: {e}") inside that specific except, and allow
other unexpected exceptions to propagate (or re-raise) so they aren't silently
swallowed.

In `@app/package.json`:
- Line 44: The package.json currently includes the redundant meta-package
"radix-ui" which duplicates the already listed individual `@radix-ui/`* packages
and increases bundle size; remove the "radix-ui" dependency entry from
package.json (the line with "radix-ui": "^1.4.3") so only the specific
`@radix-ui/`* packages (those listed earlier) remain, then run your package
manager install to update lockfile and verify builds/tests pass.

In `@app/src/App.css`:
- Around line 6-16: Replace the hard height on the root app container and
reconsider global overflow: for the .App selector change the fixed "height:
100vh" to "min-height: 100dvh" to use the dynamic viewport height on mobile
(keep existing overflow-y: auto); review the global "html, body { overflow-x:
hidden; max-width: 100vw; }" usage and remove or narrow it if it was masking
layout/overflow bugs (or add a comment explaining it's intentional) so the
root/container selectors reflect the intended behavior.

In `@app/src/components/AtcoderTable.jsx`:
- Line 16: The destructured variables open and isMobile from useSidebar() in
AtcoderTable.jsx are unused; remove them by changing the destructuring to only
grab the needed value(s) (e.g., const { /* neededProp */ } = useSidebar() or
simply const sidebar = useSidebar() if you need the whole object) or call
useSidebar() without destructuring if nothing is needed; update any references
to open or isMobile (none expected) and run lint to ensure no remaining unused
variable warnings.
- Around line 6-11: The file imports Avatar, AvatarFallback, AvatarImage and
User but they are not referenced in the AtcoderTable component's column
definitions; remove these unused imports (Avatar, AvatarFallback, AvatarImage,
User) from the top of AtcoderTable.jsx or replace the import with only symbols
actually used by the component so the linter warning goes away and dead imports
are eliminated.
- Line 102: AtcoderTable currently forces a full page reload via
window.location.reload() after adding a user; remove that call and instead
invoke a passed-in callback or trigger a local state update to refresh the list.
Update the add-user handler (e.g., the function containing
window.location.reload()) to call a prop like onUserAdded or call a local
fetchUsers/refreshData method after the add completes, or lift the users state
up and update it from the parent so the UI re-renders without a reload. Ensure
the component accepts the callback prop (onUserAdded/refreshUsers) and uses it
in place of window.location.reload() so new data is fetched/reconciled
gracefully.

In `@app/src/components/CodechefTable.jsx`:
- Line 36: In the CodechefTable component remove the unused useSidebar hook call
and its destructuring (the line "const { open, isMobile } = useSidebar();") and
also remove the corresponding import of useSidebar; this cleans up unused
variables in CodechefTable.jsx and prevents importing a hook that is no longer
referenced.

In `@app/src/components/CodeforcesTable.jsx`:
- Line 62: Remove all development console.log calls in the CodeforcesTable
component (e.g., the logs at lines 62, 80, 89, 95, 128, 140, 149) so they don't
leak to production; locate the prints inside the CodeforcesTable component
(within its useEffect hooks and any helper functions that fetch or process
userData) and either delete the console.log statements or wrap them behind a
NODE_ENV check (e.g., only call console.debug when process.env.NODE_ENV !==
'production') or replace with a proper logger if available.
- Line 26: Remove the unused useSidebar hook invocation in CodeforcesTable.jsx:
delete the line "const { open, isMobile } = useSidebar();" and any related
destructuring, and also remove the now-unused useSidebar import (and its import
specifier) from the top of the file to avoid unused-import warnings.

In `@app/src/components/GithubTable.jsx`:
- Around line 197-199: The filter callback in the setGithubfriends update uses a
misleading name "fruit"; update the callback parameter to a meaningful
identifier (e.g., "friend" or "username") so the code reads
setGithubfriends((current) => current.filter((friend) => friend !== e)); locate
this inside the component where setGithubfriends is called and replace "fruit"
with the chosen identifier in that callback only.
- Around line 205-212: The effect calls getghfriends but doesn’t include it in
the dependency array, which can cause stale closures; either wrap getghfriends
in useCallback (e.g., memoize the getghfriends function so it only changes when
its real dependencies change) and then add getghfriends to the useEffect deps,
or move the getghfriends logic inline inside the useEffect and keep the
dependency array as [isAuthenticated]; ensure setGithubfriends and
setGHshowfriends remain available from React state and update only within the
effect or the memoized function.
- Line 46: Remove the unused destructured variables from useSidebar() in
GithubTable.jsx: delete "open" and "isMobile" from the const { open, isMobile }
= useSidebar() call (or remove the useSidebar() call/import entirely if no
sidebar context is needed anywhere in this component) so the component no longer
declares unused variables and the import is cleaned up accordingly.

In `@app/src/components/GoToTop.jsx`:
- Around line 9-28: The JSX in GoToTop.jsx wraps a single button element in an
unnecessary React Fragment; remove the surrounding <>...</> and return the
<button> directly from the component (keep the existing props, inline styles,
onClick handler gotop, and child ArrowUp unchanged) so the component renders the
button without the redundant fragment.

In `@app/src/components/Heatmap.jsx`:
- Around line 21-33: The useEffect in Heatmap.jsx references
fetchGitHubContributions, fetchCodeforcesData, and generateMockData but doesn't
list them in the dependency array, causing stale closures; fix by either
wrapping those functions in useCallback (e.g., const fetchGitHubContributions =
useCallback(..., [itsDeps]), const fetchCodeforcesData = useCallback(...,
[itsDeps]), const generateMockData = useCallback(..., [itsDeps])) and then
adding them to the useEffect dependency array, or move their implementations
directly inside the useEffect so the effect only depends on platform and
username; ensure any internal state or props those functions use are included in
their useCallback dependency arrays.
- Around line 227-239: In the Heatmap component's streak-calculation loop (the
code referencing flatData, index i, level and streak), remove all commented-out
console.log debug lines and any adjacent commented debug messages (e.g., "//
console.log(`Streak incremented...`)", "// console.log(`Today (index
${i})...`)", "// console.log(`Streak broken...`)", "// console.log(`Final
calculated streak...`)") so the loop contains only active logic and necessary
comments.

In `@app/src/components/HomePage.jsx`:
- Line 37: The module-level variable "rank" is declared with var but never
reassigned; update its declaration to use const (replace the "var rank = 0;"
declaration with "const rank = 0;") in HomePage.jsx, or if rank is meant to
change, move it into component state (e.g., useState inside the relevant
component) or compute it from props instead.

In `@app/src/components/LeetcodeTable.jsx`:
- Around line 216-223: The useEffect in LeetcodeTable.jsx references the async
function getltfriends but doesn't include it in the dependency array, risking
stale closures; either add getltfriends to the dependency array of the
useEffect([isAuthenticated, getltfriends]) or memoize getltfriends with
useCallback (so its identity is stable) and then include that memoized function
in the deps; keep existing calls to setLeetcodefriends and setLTshowfriends and
the isAuthenticated check unchanged.
- Line 46: The destructured variables open and isMobile from useSidebar() are
unused; update the call in LeetcodeTable.jsx by removing those identifiers so
you only extract what’s needed (e.g., const { /*keep only used props*/ } =
useSidebar() or simply call useSidebar() without destructuring), and ensure
there are no remaining references to open or isMobile in functions or JSX (check
any usages in render, handlers, or effects that might need refactoring or safe
removal).
- Around line 208-210: The filter callback inside setLeetcodefriends uses a
misleading parameter name `fruit`; change it to a clearer name like `friend` or
`username` and update any references in the callback (e.g., the equality check
against `e`) so the code reads setLeetcodefriends((current) =>
current.filter((friend) => friend !== e)); this makes the intent in
LeetcodeTable.jsx and the setLeetcodefriends state updater clearer and
consistent with GithubTable.

In `@app/src/components/Login.jsx`:
- Line 31: The destructured variables open and isMobile from useSidebar() in the
Login component are unused; remove them from the destructuring (replace "const {
open, isMobile } = useSidebar();" with a call that only uses needed values or
simply "useSidebar();" if no values are needed) and remove the useSidebar import
if nothing from it is used elsewhere in Login.jsx; ensure you only keep the
specific hook usage required by the component (e.g., keep useSidebar() or
specific properties like isOpen if actually used).

In `@app/src/components/Navbar.jsx`:
- Line 212: The JSX conditional uses a redundant nullish coalescing expression
"(user ?? false)" — replace it with the simpler truthy check "user" wherever it
appears in the Navbar component's JSX conditional rendering so the ternary or
logical rendering uses "user" directly (look for the JSX expression using (user
?? false) in Navbar.jsx and update it to user).

In `@app/src/components/NavMenu.jsx`:
- Line 75: Remove the redundant `globalStreak >= 0` check in the NavMenu JSX
conditional — since `globalStreak` is already computed with `Math.max(..., 0)`
it will always be >= 0; update the conditional expression `{user && globalStreak
>= 0 && (...)}` to simply `{user && (...)}` referencing the `globalStreak`
variable and the JSX block in NavMenu.jsx so the UI logic relies only on `user`
for rendering.
- Around line 62-69: The Search input in NavMenu.jsx is rendered but not wired
up; add a controlled state (e.g., const [searchQuery, setSearchQuery] =
useState('')) and an onChange handler (e.g., function handleSearchChange(e) {
setSearchQuery(e.target.value); }) and pass it to the Input as
onChange={handleSearchChange} and value={searchQuery}; then either call a search
function from that handler (or a debounced wrapper) such as
fetchSearchResults(searchQuery) or invoke a prop callback (e.g.,
props.onSearch(searchQuery)) to perform filtering/fetching, and remove the input
entirely if you prefer to postpone implementing search. Ensure you reference the
Input component and the NavMenu.jsx component's user conditional when making the
change.

In `@app/src/components/PlatformStreakFetcher.jsx`:
- Around line 40-64: Extract the duplicated streak loop into a shared helper
(e.g., calculateStreakFromDays) and replace the logic inside
fetchLeetcodeStreak, fetchAtcoderStreak, and fetchCodechefStreak with calls to
that helper; the helper should accept an array of sorted descending day
timestamps (or numeric day keys converted to ms) and a Date/now value, implement
the same logic currently in the loop (initial day check using dayDiff <= 1,
skipping same-day entries, incrementing on roundedDiff === 1, breaking
otherwise) and return the computed streak number so each fetch* function simply
prepares the sorted timestamps and returns calculateStreakFromDays(sortedDays,
now).

In `@app/src/components/Profile.jsx`:
- Line 35: Remove the unused useSidebar hook call in Profile.jsx: the
destructured values open and isMobile are no longer referenced, so delete the
line "const { open, isMobile } = useSidebar();" (and the import of useSidebar if
it becomes unused) to prevent unnecessary re-renders and clean up the component.
Ensure no other references to open or isMobile remain before removing the
import.

In `@app/src/components/Register.jsx`:
- Line 43: The destructured variables open and isMobile from useSidebar() in
Register.jsx are unused; remove the unnecessary destructuring (and the
useSidebar import if it becomes unused) to eliminate dead code. Locate the line
"const { open, isMobile } = useSidebar();" and replace it with either a no-op
call to useSidebar() if side effects are needed or remove the call entirely, and
then remove the corresponding import of useSidebar from the top of the file if
nothing else uses it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7d77988-da8e-4a9d-b2d3-c04504fe2658

📥 Commits

Reviewing files that changed from the base of the PR and between b2f1a42 and e5a5b17.

⛔ Files ignored due to path filters (1)
  • app/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • README.md
  • api/leaderboard/models.py
  • api/leaderboard/users.py
  • api/leaderboard/views.py
  • app/.env.frontend
  • app/package.json
  • app/src/App.css
  • app/src/App.jsx
  • app/src/components/AtcoderTable.jsx
  • app/src/components/CodechefTable.jsx
  • app/src/components/CodeforcesTable.jsx
  • app/src/components/GithubTable.jsx
  • app/src/components/GoToTop.jsx
  • app/src/components/Heatmap.jsx
  • app/src/components/HomePage.jsx
  • app/src/components/LeetcodeTable.jsx
  • app/src/components/Login.jsx
  • app/src/components/NavMenu.jsx
  • app/src/components/Navbar.jsx
  • app/src/components/PlatformStreakFetcher.jsx
  • app/src/components/Profile.jsx
  • app/src/components/Register.jsx
  • app/src/components/ui/sidebar.jsx
  • app/src/index.css
💤 Files with no reviewable changes (1)
  • app/.env.frontend

Comment on lines +118 to +127
{items.map((item, index) => {
const isActive = pathname === item.url;
const isBlogs = index === 7;
return (
<SidebarMenuItem
key={item.title}
className={cn(
"relative",
isBlogs && "mt-8" // Add distance before Blogs
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Off-by-one error: Blogs is at index 6, not 7.

The items array has 7 elements (indices 0-6). Blogs is at index 6, so index === 7 will never be true and the mt-8 spacing will never be applied.

🐛 Proposed fix
               {items.map((item, index) => {
                 const isActive = pathname === item.url;
-                const isBlogs = index === 7;
+                const isBlogs = index === 6;
                 return (
📝 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
{items.map((item, index) => {
const isActive = pathname === item.url;
const isBlogs = index === 7;
return (
<SidebarMenuItem
key={item.title}
className={cn(
"relative",
isBlogs && "mt-8" // Add distance before Blogs
)}
{items.map((item, index) => {
const isActive = pathname === item.url;
const isBlogs = index === 6;
return (
<SidebarMenuItem
key={item.title}
className={cn(
"relative",
isBlogs && "mt-8" // Add distance before Blogs
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Navbar.jsx` around lines 118 - 127, The off-by-one bug
prevents the Blogs spacing because isBlogs is set with index === 7 but Blogs is
at index 6; update the condition in the map (where isActive and isBlogs are
computed for items.map) to correctly identify Blogs — either change index === 7
to index === 6 or, more robustly, check the item identity (e.g., item.title ===
"Blogs" or item.url === "/blogs") before passing the className to
SidebarMenuItem so the "mt-8" spacing is applied to the Blogs entry.

Comment thread app/src/components/Navbar.jsx Outdated
Comment on lines +134 to +190
const fetchCodechefStreak = async () => {
try {
const response = await fetch(`https://codechef-api.vercel.app/handle/${username}`);
const data = await response.json();

if (data && data.heatMap) {
// heatMap is an array of { date: "YYYY-MM-DD", value: number }
const sortedEntries = data.heatMap
.filter(entry => entry.value > 0)
.sort((a, b) => new Date(b.date) - new Date(a.date));

if (sortedEntries.length === 0) {
updateStreak(platform, 0);
return;
}

let streak = 0;
const now = new Date();
now.setHours(0, 0, 0, 0);

for (let i = 0; i < sortedEntries.length; i++) {
const entryDate = new Date(sortedEntries[i].date);
entryDate.setHours(0, 0, 0, 0);

if (streak === 0) {
const diffTime = now.getTime() - entryDate.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));

if (diffDays <= 1) { // Today or Yesterday
streak = 1;
} else {
break;
}
} else {
const prevDate = new Date(sortedEntries[i-1].date);
prevDate.setHours(0, 0, 0, 0);
const diffTime = prevDate.getTime() - entryDate.getTime();
const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));

if (diffDays === 1) {
streak++;
} else if (diffDays === 0) {
continue; // Same day, skip
} else {
break;
}
}
}
updateStreak(platform, streak);
} else {
updateStreak(platform, 0);
}
} catch (error) {
console.error("Error fetching CodeChef streak:", error);
updateStreak(platform, 0);
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify CodeChef API response structure
curl -s "https://codechef-api.vercel.app/handle/tourist" | jq 'keys'

Repository: OpenLake/Leaderboard-Pro

Length of output: 124


🏁 Script executed:

# Get raw CodeChef API response
curl -s "https://codechef-api.vercel.app/handle/tourist"

Repository: OpenLake/Leaderboard-Pro

Length of output: 145


🏁 Script executed:

# Search codebase for CodeChef API references and heatMap usage
rg -i "heatmap\|codechef.*api" --type js --type jsx -A 3 -B 3

Repository: OpenLake/Leaderboard-Pro

Length of output: 95


🏁 Script executed:

# Check the views.py file mentioned in the review for context
cat -n api/leaderboard/views.py | sed -n '259,280p'

Repository: OpenLake/Leaderboard-Pro

Length of output: 1091


🏁 Script executed:

# Search for heatMap references anywhere in the codebase
rg "heatMap|heatmap" --type-list

Repository: OpenLake/Leaderboard-Pro

Length of output: 457


🏁 Script executed:

# Search for heatMap in all file types
find . -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.py" -o -name "*.json" \) | xargs grep -l "heatMap\|heatmap" 2>/dev/null

Repository: OpenLake/Leaderboard-Pro

Length of output: 183


🏁 Script executed:

# Check PlatformStreakFetcher.jsx to see full context of all streak fetch functions
cat app/src/components/PlatformStreakFetcher.jsx | head -200

Repository: OpenLake/Leaderboard-Pro

Length of output: 7382


CodeChef API does not return heatMap field — CodeChef streak will always be 0.

The CodeChef API endpoint (https://codechef-api.vercel.app/handle/{username}) returns user rating data (currentRating, highestRating, globalRank, countryRank, profile) as confirmed in api/leaderboard/views.py:263-277. It does not return a heatMap array. Since the frontend code checks for data.heatMap at line 139, this condition will always be false, causing the streak to always be set to 0 (line 161).

The CodeChef streak feature is fundamentally broken due to an API response mismatch. Either the API endpoint must be changed to return submission activity data, or the streak calculation logic must be rewritten to work with the available rating/ranking data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/PlatformStreakFetcher.jsx` around lines 134 - 190, The
fetchCodechefStreak function assumes data.heatMap exists (so streak logic never
runs); fix by either (A) switching the fetch URL to an endpoint that returns
submission/activity dates and update fetchCodechefStreak to parse that response
(e.g., request an activity/submissions API that returns dates and build the
sortedEntries from that), or (B) change the backend API to include a
heatMap/activity array for the existing endpoint so fetchCodechefStreak can use
it; in either case remove the data.heatMap guard and instead parse the actual
activity field returned (or return early with updateStreak(platform, 0) only
when that activity array is absent/empty), keeping function name
fetchCodechefStreak and final updateStreak(platform, streak) usage. Ensure error
handling stays and add a small comment in fetchCodechefStreak indicating which
field it expects from the API.

Comment thread app/src/index.css
Comment on lines +5 to +14
html, body {
margin: 0;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
height: 100%;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify GoToTop component uses window.scrollTo
rg -n "window.scrollTo" app/src/components/GoToTop.jsx

# Check if there are other components using window scroll APIs
rg -n "window\.(scrollTo|scroll|scrollY|scrollX|pageYOffset)" app/src/

Repository: OpenLake/Leaderboard-Pro

Length of output: 226


🏁 Script executed:

cat app/src/App.css | head -50

Repository: OpenLake/Leaderboard-Pro

Length of output: 745


🏁 Script executed:

# Check for scroll event listeners or other scroll-related code
rg -n "scroll" app/src/ --type css --type jsx --type js -i | head -30

Repository: OpenLake/Leaderboard-Pro

Length of output: 95


🏁 Script executed:

# Check git diff for index.css
git diff HEAD app/src/index.css

Repository: OpenLake/Leaderboard-Pro

Length of output: 50


🏁 Script executed:

# Read full index.css to see complete context
cat app/src/index.css

Repository: OpenLake/Leaderboard-Pro

Length of output: 4569


🏁 Script executed:

cat public/index.html

Repository: OpenLake/Leaderboard-Pro

Length of output: 117


🏁 Script executed:

# Check git log to see recent changes to index.css
git log --oneline -10 app/src/index.css

Repository: OpenLake/Leaderboard-Pro

Length of output: 77


🏁 Script executed:

# Find HTML files in the repository
find . -name "*.html" -type f | head -20

Repository: OpenLake/Leaderboard-Pro

Length of output: 216


🏁 Script executed:

# Check the app structure - look for index.jsx or main entry point
find app/src -name "index.*" -o -name "App.*" | grep -E "\.(jsx|js|tsx|ts)$"

Repository: OpenLake/Leaderboard-Pro

Length of output: 122


🏁 Script executed:

cat app/index.html

Repository: OpenLake/Leaderboard-Pro

Length of output: 1423


🏁 Script executed:

head -20 app/src/index.jsx

Repository: OpenLake/Leaderboard-Pro

Length of output: 472


🏁 Script executed:

# Check the GoToTop component to see the context
cat app/src/components/GoToTop.jsx

Repository: OpenLake/Leaderboard-Pro

Length of output: 816


🏁 Script executed:

# Check if GoToTop is imported/used in App.jsx
rg -n "GoToTop" app/src/

Repository: OpenLake/Leaderboard-Pro

Length of output: 291


window.scrollTo() will not work with current CSS configuration.

The html, body element has overflow: hidden, making it non-scrollable. The actual scroll container is .App which has overflow-y: auto and height: 100vh. When GoToTop.jsx calls window.scrollTo({ left: 0, top: 0, behavior: "smooth" }), it attempts to scroll the window/html element, which has overflow: hidden and therefore cannot scroll. The scroll-to-top button will silently fail.

To fix this, either:

  • Remove overflow: hidden from html, body in index.css
  • Update GoToTop.jsx to scroll .App instead of window
🧰 Tools
🪛 Stylelint (17.4.0)

[error] 8-8: Unexpected quotes around "Roboto" (font-family-name-quotes)

(font-family-name-quotes)


[error] 8-8: Unexpected quotes around "Oxygen" (font-family-name-quotes)

(font-family-name-quotes)


[error] 9-9: Unexpected quotes around "Ubuntu" (font-family-name-quotes)

(font-family-name-quotes)


[error] 9-9: Unexpected quotes around "Cantarell" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/index.css` around lines 5 - 14, The css sets html, body { overflow:
hidden } which prevents window.scrollTo in GoToTop.jsx from working because the
scrollable container is .App (overflow-y: auto, height: 100vh); fix by either
removing overflow: hidden from the html, body rule in index.css so window
scrolling works, or modify GoToTop.jsx to target the .App element (query/select
the element with class "App" and call its scrollTo or set scrollTop with smooth
behavior) instead of calling window.scrollTo; update references to
window.scrollTo({ left:0, top:0, behavior:"smooth" }) to use the .App element's
scrolling API when choosing that approach.

Comment thread README.md Outdated
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
api/leaderboard/views.py (1)

596-630: Extract the AtCoder scraper into one shared helper.

This parsing logic is now duplicated almost verbatim here and in api/leaderboard/celery.py:283-304. The next markup fix will have to land twice, and it is easy for the API path and worker path to drift again. Centralizing the scrape/parsing code will make future fixes much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 596 - 630, The AtCoder
scraping/parsing logic in get_atcoder_data is duplicated in the Celery worker;
extract the shared HTML parsing into a single helper (e.g.,
parse_atcoder_profile or parse_atcoder_html) that accepts the page HTML or
BeautifulSoup and returns the same instance dict keys
("rating","highest_rating","rank"), then update get_atcoder_data to call that
helper after fetching page.text and update the duplicate code in the Celery task
to reuse the helper too; ensure the new helper is placed in a common module
imported by both (and update imports) so future markup fixes are applied once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/leaderboard/celery.py`:
- Around line 283-304: The scrape loop builds an instance dict but may append
incomplete records; ensure the code validates that instance contains all
required keys ("rating", "highest_rating", "rank") before appending and treat a
missing field as a failure that aborts the Celery update (e.g., raise an
exception or return an error) so the fallback preserves the previous record;
update the logic around the instance append in the table-parsing block (and the
similar logic around lines handling the other table at 306-307) to perform this
validation and trigger failure when any of those three fields are absent.

In `@api/leaderboard/views.py`:
- Around line 637-646: The request handler is performing live AtCoder refreshes
by iterating active_users and calling get_atcoder_data when
AtcoderUser.is_outdated is true, which conflicts with the background beat job
cadence; change the view to be read-only and avoid in-request refreshes: stop
calling get_atcoder_data and user.save() inside the loop (keep returning cached
user.rating/highest_rating/rank), or alternatively align the staleness window by
making the is_outdated threshold match the beat job schedule so the view will
rarely trigger updates; reference AtcoderUser.is_outdated, the loop over
active_users, and the get_atcoder_data call to locate the change.
- Around line 99-113: The GET handler currently iterates gh_users and calls
fetch_github_data for each user, causing O(n) outbound HTTP I/O; remove that
per-request refresh loop from GithubUserAPI.get() so the view becomes read-only
and only reads from the DB, and instead schedule updates to run asynchronously
(e.g., enqueue tasks that call a background worker function like
refresh_github_user_data(username) which wraps fetch_github_data + save). If you
need on-demand refreshing, expose a separate endpoint or a targeted POST that
enqueues a refresh for a single user (calling the same background task) rather
than performing network I/O inside the GET.

---

Nitpick comments:
In `@api/leaderboard/views.py`:
- Around line 596-630: The AtCoder scraping/parsing logic in get_atcoder_data is
duplicated in the Celery worker; extract the shared HTML parsing into a single
helper (e.g., parse_atcoder_profile or parse_atcoder_html) that accepts the page
HTML or BeautifulSoup and returns the same instance dict keys
("rating","highest_rating","rank"), then update get_atcoder_data to call that
helper after fetching page.text and update the duplicate code in the Celery task
to reuse the helper too; ensure the new helper is placed in a common module
imported by both (and update imports) so future markup fixes are applied once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 875c30fe-0122-4727-aff1-ba5d0b5fb151

📥 Commits

Reviewing files that changed from the base of the PR and between e5a5b17 and c179d0f.

📒 Files selected for processing (4)
  • api/leaderboard/celery.py
  • api/leaderboard/migrations/0012_alter_codeforcesuser_last_activity.py
  • api/leaderboard/settings.py
  • api/leaderboard/views.py

Comment thread api/leaderboard/celery.py
Comment thread api/leaderboard/views.py Outdated
Comment thread api/leaderboard/views.py Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
api/leaderboard/views.py (1)

742-748: ⚠️ Potential issue | 🟠 Major

Field name mismatch: "description" should be "discription".

The ReplyPost model defines the field as discription (with typo), but line 743 uses "description". This means setattr will create a new attribute on the instance rather than updating the actual model field, and the change won't be persisted to the database.

🔧 Proposed fix
         # Update allowed fields
-        for field in ["description", "likes", "dislikes"]:
+        for field in ["discription", "likes", "dislikes"]:
             if field in request.data:
                 setattr(reply, field, request.data[field])

Note: The model field name itself contains a typo (discription instead of description). Consider creating a migration to rename the field for long-term maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 742 - 748, The loop is updating
"description" which mismatches the ReplyPost model's actual field name
"discription", causing setattr to create a non-persistent attribute; update the
allowed fields list in the view to use "discription" (replace "description" with
"discription") so setattr(reply, "discription", ...) updates the real model
field before reply.save(), and consider creating a follow-up migration to rename
the model field for long-term correctness; relevant symbols: reply,
ReplyPost_Serializer, and the model field discription.
app/src/components/CodeforcesTable.jsx (3)

839-868: ⚠️ Potential issue | 🟠 Major

Race condition: setContestId is async, so fetchContestStandings() uses stale value.

When clicking the quick contest buttons, setContestId is called followed immediately by fetchContestStandings(). Since React state updates are asynchronous, fetchContestStandings will use the old contestId value, not the newly set one.

🔧 Proposed fix - pass contest ID directly
-                  <Button 
-                    variant="outline" 
-                    size="sm"
-                    onClick={() => {
-                      setContestId("1902");
-                      fetchContestStandings();
-                    }}
-                  >
+                  <Button 
+                    variant="outline" 
+                    size="sm"
+                    onClick={() => {
+                      setContestId("1902");
+                    }}
+                  >
                     1902 (Div. 3)
                   </Button>

Then use a useEffect to trigger fetch when contestId changes, or modify fetchContestStandings to accept an optional contest ID parameter:

const fetchContestStandings = async (overrideId) => {
  const idToFetch = overrideId ?? contestId;
  if (!idToFetch) { ... }
  // use idToFetch instead of contestId
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` around lines 839 - 868, The
quick-button handlers race because setContestId is async so
fetchContestStandings reads the old contestId; fix by changing
fetchContestStandings to accept an optional overrideId (e.g., function
fetchContestStandings(overrideId) { const idToFetch = overrideId ?? contestId;
... }) and update the button onClick handlers to call
fetchContestStandings("1902") / fetchContestStandings("1907") /
fetchContestStandings("1073") after calling setContestId, or alternatively
remove the immediate fetch and rely on a useEffect watching contestId
(useEffect(() => { if (contestId) fetchContestStandings(); }, [contestId])) so
the fetch always uses the latest contestId.

148-152: ⚠️ Potential issue | 🟠 Major

Fallback to first user is misleading and incorrect.

If user data cannot be fetched, falling back to codeforcesUsers[0] will display a random user's data as "You". This could confuse users and show incorrect information. The fallback should be null or show an error state instead.

🔧 Proposed fix
         // If we get here, something went wrong
         console.warn("Could not fetch logged in user data");
-        if (codeforcesUsers.length > 0) {
-          setLoggedInUser(codeforcesUsers[0]);
-        }
+        setLoggedInUser(null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` around lines 148 - 152, The current
fallback sets setLoggedInUser(codeforcesUsers[0]) when fetching user data fails,
which incorrectly shows a random user as "You"; change this to clear the
logged-in user and surface an error state instead. In the CodeforcesTable.jsx
component replace the fallback call to setLoggedInUser(codeforcesUsers[0]) with
setLoggedInUser(null) (or an explicit error flag/state like
setUserFetchError(true)), and ensure the render logic that reads loggedInUser
handles null/error (e.g., shows an error message or login prompt) so no other
component will display another user's data as the current user.

96-99: ⚠️ Potential issue | 🟠 Major

The timeout option is not supported by the native fetch API and will be silently ignored.

The { timeout: 10000 } option passed to fetch() has no effect. Native fetch does not support a timeout parameter in its options object. This pattern appears three times (lines 96-99, 109-112, 176-179).

Use AbortSignal instead to implement request timeouts:

Fix using AbortController
-            const cfResponse = await fetch(
-              `https://codeforces.com/api/user.info?handles=${currentUsername}`,
-              { timeout: 10000 }
-            );
+            const controller = new AbortController();
+            const timeoutId = setTimeout(() => controller.abort(), 10000);
+            try {
+              const cfResponse = await fetch(
+                `https://codeforces.com/api/user.info?handles=${currentUsername}`,
+                { signal: controller.signal }
+              );
+              clearTimeout(timeoutId);

Consider extracting a helper function to avoid repetition across all three instances.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` around lines 96 - 99, The fetch calls
in CodeforcesTable.jsx (the cfResponse fetch and the two other similar fetch
calls) currently pass a non‑existent { timeout: 10000 } option which is ignored;
replace this pattern by creating a small helper (e.g., fetchWithTimeout) that
uses AbortController to set a 10s timeout, pass controller.signal to fetch, and
clear the timeout on completion; then update the three places (the cfResponse
fetch and the two other fetch invocations in the component) to call
fetchWithTimeout(url, { method, ... }) so all requests properly abort after 10s.
🧹 Nitpick comments (6)
app/src/components/Blogs.jsx (1)

220-229: Client-side guard is good but insufficient without backend enforcement.

The conditional currentUsername === blog.username correctly restricts the delete button visibility to the blog author. However, this is a cosmetic check only — security must be enforced server-side (as noted above).

Minor UX note: Consider using a Dialog component for delete confirmation instead of window.confirm() to maintain consistency with the "Create Blog" dialog pattern used elsewhere in this component.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 220 - 229, The client-side check
currentUsername === blog.username only hides the delete button but does not
enforce security; update the backend delete handler (the API route/controller
that handles blog deletion) to verify the authenticated user matches
blog.username and return 403/unauthorized if not, and add tests for unauthorized
delete attempts; on the frontend, replace the window.confirm() flow inside
handleDeleteBlog with the app's Dialog-based confirmation (the same Dialog
pattern used for the "Create Blog" dialog in Blogs.jsx) so deletion uses a
consistent modal confirmation UI and then call the delete API only after
positive confirmation.
app/src/components/Friends.jsx (1)

48-60: Sequential fetching could be parallelized for better performance.

The loadAllFriends function awaits each fetchFriends call sequentially, which is slower than necessary. Since the requests are independent, they can run in parallel.

⚡ Proposed optimization using Promise.all
   useEffect(() => {
     const loadAllFriends = async () => {
       setLoading(true);
-      for (const platform of PLATFORMS) {
-        await fetchFriends(platform.id);
-      }
+      await Promise.all(PLATFORMS.map(platform => fetchFriends(platform.id)));
       setLoading(false);
     };

     if (user) {
       loadAllFriends();
     }
   }, [user]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Friends.jsx` around lines 48 - 60, The current
loadAllFriends function runs fetchFriends sequentially by awaiting each call in
a for loop; replace that with parallel execution using Promise.all to invoke
fetchFriends for every PLATFORMS item (e.g., map PLATFORMS to platform.id and
call fetchFriends) so all requests run concurrently, and keep the
setLoading(true) before launching Promise.all and setLoading(false) after
awaiting Promise.all to preserve loading state; update the useEffect block that
calls loadAllFriends to use the new parallel implementation.
api/leaderboard/views.py (2)

813-817: Consider adding pagination for scalability.

The UserNamesList endpoint returns all UserNames objects without pagination. As the user base grows, this could lead to performance issues and large response payloads.

💡 Example with pagination
from rest_framework.pagination import PageNumberPagination

class UserNamesList(APIView):
    def get(self, request):
        usernames = UserNames.objects.all()
        paginator = PageNumberPagination()
        paginator.page_size = 50
        page = paginator.paginate_queryset(usernames, request)
        serializer = UserNamesSerializer(page, many=True)
        return paginator.get_paginated_response(serializer.data)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 813 - 817, The UserNamesList.get
currently returns all UserNames objects causing unbounded payloads; update
UserNamesList.get to use DRF pagination (e.g., PageNumberPagination) by creating
a paginator, setting a suitable page_size (e.g., 50), calling
paginator.paginate_queryset(UserNames.objects.all(), request) and passing the
resulting page to UserNamesSerializer(many=True), then return
paginator.get_paginated_response(serializer.data) instead of
Response(serializer.data); reference class UserNamesList, method get, model
UserNames and serializer UserNamesSerializer when making the change.

658-658: Remove duplicate import.

IsAuthenticatedOrReadOnly is already imported at line 13. This duplicate import is unnecessary.

🧹 Proposed fix
-from rest_framework.permissions import IsAuthenticatedOrReadOnly
-
 class AtcoderViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` at line 658, Remove the duplicate import of
IsAuthenticatedOrReadOnly from rest_framework.permissions — keep the single
existing import (the one at the top of the file) and delete the later redundant
line `from rest_framework.permissions import IsAuthenticatedOrReadOnly`; verify
no other imports were accidentally removed and run linters/tests to confirm no
import errors in views referencing IsAuthenticatedOrReadOnly.
app/src/components/CodeforcesTable.jsx (1)

289-291: Missing dependency in useEffect may cause stale data.

The useEffect calling getcffriends() has an empty dependency array, but getcffriends checks isAuthenticated. If the user logs in after component mount, friends won't be fetched. Consider adding isAuthenticated to the dependency array.

♻️ Proposed fix
   useEffect(() => {
     getcffriends();
-  }, []);
+  }, [isAuthenticated]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/CodeforcesTable.jsx` around lines 289 - 291, The effect
that calls getcffriends() uses an empty dependency array so it won't re-run when
authentication changes; update the useEffect that currently calls getcffriends()
to include isAuthenticated in its dependency array and ensure getcffriends is
stable (either memoize it with useCallback or move the fetch logic inline inside
the useEffect) so friends are fetched when isAuthenticated becomes true;
reference the existing getcffriends function and the useEffect that calls it to
locate where to change the dependency array.
api/leaderboard/models.py (1)

194-203: Consider adding validation for slug and tier fields.

The Achievement model accepts arbitrary strings for slug and tier without validation. This could lead to inconsistent data (e.g., typos like "bronz" vs "bronze", or invalid achievement slugs). Consider adding choices or a validator to ensure data integrity.

💡 Example with choices
class Achievement(models.Model):
    TIER_CHOICES = [
        ("bronze", "Bronze"),
        ("silver", "Silver"),
        ("gold", "Gold"),
        ("platinum", "Platinum"),
    ]
    
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    slug = models.CharField(max_length=64)
    tier = models.CharField(max_length=64, choices=TIER_CHOICES)
    unlocked_at = models.DateTimeField(auto_now_add=True)

Alternatively, create a separate AchievementDefinition model and use a ForeignKey for slug.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/models.py` around lines 194 - 203, The Achievement model
currently allows arbitrary strings for slug and tier; add validation by defining
a canonical set of tiers (e.g., TIER_CHOICES) and applying it to the tier field
(tier = models.CharField(..., choices=TIER_CHOICES)) and either add a validator
(RegexValidator or custom validator) to the slug field or replace slug with a
ForeignKey to a new AchievementDefinition model to enforce valid slugs; update
the Achievement class (fields slug, tier and any import of validators or new
AchievementDefinition) accordingly and ensure migrations are created.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/leaderboard/celery.py`:
- Around line 187-202: The task computes gh_user.stars incorrectly by using
fetch_starred_repos (count of repos the user has starred) instead of summing
stargazers_count like github_user_update; replace that logic by calling a
function that paginates the user's repos and sums repo["stargazers_count"]
(e.g., implement fetch_user_repo_stars or reuse the repo-summing logic from
github_user_update) and assign its result to gh_user.stars while keeping
gh_user.repositories and fetch_github_contributions as-is.
- Around line 179-195: fetch_github_contributions, fetch_starred_repos, and the
direct requests.get(url) call do not set timeouts and fetch_starred_repos
targets the "starred by user" endpoint instead of summing stars on the user's
repos; add a sensible timeout (e.g. timeout=5) to every requests.get invocation
in these functions and replace fetch_starred_repos implementation to call the
user's repos endpoint (https://api.github.com/users/{username}/repos), iterate
the returned repo objects and sum the stargazers_count (handle pagination if
needed), returning that sum — keep function names fetch_github_contributions and
fetch_starred_repos (or rename to fetch_repo_stars if preferred) but ensure the
three requests.get calls include the timeout param and the stars metric uses
stargazers_count from the repos response.

In `@api/leaderboard/friends.py`:
- Around line 489-502: The current atcoderFriendAddition handler mutates a list
from find_one and then $set which can race; replace that logic by performing an
atomic update on the atcoderFriends collection: call update_one(filter={"_id":
user}, update={"$addToSet": {"Friends": friendName}}, upsert=True) instead of
reading and mutating the Friends list, and similarly change
atcoderFriendDeletion to call update_one(filter={"_id": user}, update={"$pull":
{"Friends": friendName}}) so removals are atomic; remove the
find_one/mutate/$set branches and any insert_one path and rely on the collection
update operations.
- Around line 514-515: The code is incorrectly wrapping serializer.errors in
braces which attempts to create a set from a dict and raises TypeError; for
every occurrence replace Response({serializer.errors},
status=status.HTTP_400_BAD_REQUEST) with Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST) so the field-level validation errors are
returned correctly; search for usages of "Response({serializer.errors}" (and
similarly where serializer.errors is wrapped in {}), and update them to pass
serializer.errors directly while keeping the same status.HTTP_400_BAD_REQUEST
and surrounding logic intact.

In `@api/leaderboard/management/commands/update_db.py`:
- Around line 9-15: The import list in update_db.py dropped codechefUser but the
function codechef_user_update() still references codechefUser, causing a
NameError; restore codechefUser to the import tuple (alongside LeetcodeUser,
codeforcesUser, githubUser, openlakeContributor, AtcoderUser) or else refactor
codechef_user_update() to stop referencing codechefUser (e.g., remove or replace
usages), ensuring the symbol codechefUser is defined before being dereferenced.
- Around line 305-344: The scrape builds an instance dict but appends it even
when rating/highest_rating/rank are missing, allowing partial payloads to
overwrite good data; modify the try block after the table loop to validate that
instance contains all three keys ("rating", "highest_rating", "rank") before
appending to updates (or else raise/handle as a scrape failure), e.g., check the
presence of those keys on instance built in the try for ac_user and only call
updates.append(instance) when all are present; otherwise fall back to creating
the fallback instance using ac_user.rating/ac_user.highest_rating/ac_user.rank
(the same object currently built in the except) or re-raise to hit the except
handler so AC_Update_Serializer never receives partial data.

In `@api/leaderboard/root.py`:
- Around line 62-70: Add the main AtCoder leaderboard entry to the root links
mapping: insert an "atcoder-leaderboard" key alongside the existing
"atcoder-friend-addition"/"atcoder-friend-deletion"/"atcoder-friend-list"
entries and set its value to a reverse(...) call for the AtCoder leaderboard
view using the same request and format arguments (i.e.,
reverse("<ATCODER_LEADERBOARD_URL_NAME>", request=request, format=format));
replace <ATCODER_LEADERBOARD_URL_NAME> with the actual URL name used by the
leaderboard view so clients discovering via the API root will receive the
AtCoder leaderboard URL.

In `@api/leaderboard/serializers.py`:
- Around line 142-151: DiscussionPost and ReplyPost are being created without
setting the author FK, causing username to be NULL and the ReadOnlyField source
"username.username" to fail; update the creation calls in
DiscussionPostManage.post and DiscussionReplyManage.post so the calls to
DiscussionPost.objects.create(...) and ReplyPost.objects.create(...) include
username=request.user (i.e., pass the current request.user as the
username/author FK when creating the objects).

In `@api/leaderboard/views.py`:
- Around line 348-350: The code uses timedelta in the loop that updates outdated
users (the for user in cc_users.filter(... last_updated__lt=datetime.now() -
timedelta(minutes=15))[:5] block) but timedelta is not imported; fix by adding
timedelta to the datetime imports (e.g., include "timedelta" in the existing
datetime import at the top of api/leaderboard/views.py) so the symbol timedelta
is defined when the cc_users filter runs.

In `@app/src/components/Blogs.jsx`:
- Around line 108-131: Add authentication and server-side authorization:
decorate the DiscussionPostManage view (class) with
`@permission_classes`([IsAuthenticated]) and in its delete method
(DiscussionPostManage.delete) load the DiscussionPost by title/ID and verify
request.user == post.author before performing deletion; return 403 if not
authorized. Update the frontend handleDeleteBlog in Blogs.jsx to include the
auth token in request headers (e.g., Authorization: Bearer <token> and existing
Content-Type) so the backend can authenticate the caller. Also ensure the
backend uses the authenticated user rather than trusting username in
request.data when determining which post to delete.

In `@app/src/components/Friends.jsx`:
- Around line 204-206: AvatarFallback currently uses handle[0].toUpperCase()
which will throw if handle is an empty string or falsy; change the rendering to
safely derive the initial by checking handle (e.g., const initial = handle &&
handle.length ? handle[0].toUpperCase() : '?' ) and use that initial inside
AvatarFallback (update the code around Avatar / AvatarFallback to reference the
new safe initial), ensuring you also handle null/undefined by falling back to a
default character.
- Around line 12-18: PLATFORMS is missing an Atcoder entry so the Friends
component can't call the backend atcoder endpoints; add an object to the
PLATFORMS array in Friends.jsx with id "atcoder" and appropriate name/icon/color
and set suffix to match the backend pattern ("atcoder") so the component builds
URLs like atcoderFA/ (or alternatively align the backend URL pattern to use the
short suffix if you prefer); update any UI code that expects the suffix string
to ensure it uses the new "atcoder" suffix when constructing friend API routes.
- Around line 32-38: The code currently calls
JSON.parse(localStorage.getItem("authTokens")) directly (in the fetch block and
at other sites) which can throw on null or corrupted data; add a small safe
helper function (e.g., getAuthTokens) that wraps localStorage.getItem in a
try/catch and returns null on errors, then replace direct JSON.parse calls with
getAuthTokens() and guard uses of authTokens (e.g., Authorization header
creation in the fetch call and any access to authTokens.access) so you only set
the header or proceed when getAuthTokens() returns a non-null object; update all
occurrences referenced in this file (the fetch block and the other spots around
lines with JSON.parse) to use this helper.

In `@app/src/components/Navbar.jsx`:
- Around line 118-120: The active-state logic marks multiple items active on "/"
because isActive is set by a simple pathname === item.url; update the logic
inside items.map (where isActive and isBlogs are computed) so only one item
becomes active for the root: either give placeholder entries distinct routes
(change their item.url values from "/" to unique routes) or special-case the
home path by changing the isActive condition to something like pathname ===
item.url && !(pathname === "/" && index !== <desiredHomeIndex>) so only the
intended index (e.g., Dashboard) is active when pathname is "/". Ensure you
update references within the same map block (isActive, item.url, index) and
remove the ambiguous placeholders if you choose to assign unique routes.

---

Outside diff comments:
In `@api/leaderboard/views.py`:
- Around line 742-748: The loop is updating "description" which mismatches the
ReplyPost model's actual field name "discription", causing setattr to create a
non-persistent attribute; update the allowed fields list in the view to use
"discription" (replace "description" with "discription") so setattr(reply,
"discription", ...) updates the real model field before reply.save(), and
consider creating a follow-up migration to rename the model field for long-term
correctness; relevant symbols: reply, ReplyPost_Serializer, and the model field
discription.

In `@app/src/components/CodeforcesTable.jsx`:
- Around line 839-868: The quick-button handlers race because setContestId is
async so fetchContestStandings reads the old contestId; fix by changing
fetchContestStandings to accept an optional overrideId (e.g., function
fetchContestStandings(overrideId) { const idToFetch = overrideId ?? contestId;
... }) and update the button onClick handlers to call
fetchContestStandings("1902") / fetchContestStandings("1907") /
fetchContestStandings("1073") after calling setContestId, or alternatively
remove the immediate fetch and rely on a useEffect watching contestId
(useEffect(() => { if (contestId) fetchContestStandings(); }, [contestId])) so
the fetch always uses the latest contestId.
- Around line 148-152: The current fallback sets
setLoggedInUser(codeforcesUsers[0]) when fetching user data fails, which
incorrectly shows a random user as "You"; change this to clear the logged-in
user and surface an error state instead. In the CodeforcesTable.jsx component
replace the fallback call to setLoggedInUser(codeforcesUsers[0]) with
setLoggedInUser(null) (or an explicit error flag/state like
setUserFetchError(true)), and ensure the render logic that reads loggedInUser
handles null/error (e.g., shows an error message or login prompt) so no other
component will display another user's data as the current user.
- Around line 96-99: The fetch calls in CodeforcesTable.jsx (the cfResponse
fetch and the two other similar fetch calls) currently pass a non‑existent {
timeout: 10000 } option which is ignored; replace this pattern by creating a
small helper (e.g., fetchWithTimeout) that uses AbortController to set a 10s
timeout, pass controller.signal to fetch, and clear the timeout on completion;
then update the three places (the cfResponse fetch and the two other fetch
invocations in the component) to call fetchWithTimeout(url, { method, ... }) so
all requests properly abort after 10s.

---

Nitpick comments:
In `@api/leaderboard/models.py`:
- Around line 194-203: The Achievement model currently allows arbitrary strings
for slug and tier; add validation by defining a canonical set of tiers (e.g.,
TIER_CHOICES) and applying it to the tier field (tier = models.CharField(...,
choices=TIER_CHOICES)) and either add a validator (RegexValidator or custom
validator) to the slug field or replace slug with a ForeignKey to a new
AchievementDefinition model to enforce valid slugs; update the Achievement class
(fields slug, tier and any import of validators or new AchievementDefinition)
accordingly and ensure migrations are created.

In `@api/leaderboard/views.py`:
- Around line 813-817: The UserNamesList.get currently returns all UserNames
objects causing unbounded payloads; update UserNamesList.get to use DRF
pagination (e.g., PageNumberPagination) by creating a paginator, setting a
suitable page_size (e.g., 50), calling
paginator.paginate_queryset(UserNames.objects.all(), request) and passing the
resulting page to UserNamesSerializer(many=True), then return
paginator.get_paginated_response(serializer.data) instead of
Response(serializer.data); reference class UserNamesList, method get, model
UserNames and serializer UserNamesSerializer when making the change.
- Line 658: Remove the duplicate import of IsAuthenticatedOrReadOnly from
rest_framework.permissions — keep the single existing import (the one at the top
of the file) and delete the later redundant line `from
rest_framework.permissions import IsAuthenticatedOrReadOnly`; verify no other
imports were accidentally removed and run linters/tests to confirm no import
errors in views referencing IsAuthenticatedOrReadOnly.

In `@app/src/components/Blogs.jsx`:
- Around line 220-229: The client-side check currentUsername === blog.username
only hides the delete button but does not enforce security; update the backend
delete handler (the API route/controller that handles blog deletion) to verify
the authenticated user matches blog.username and return 403/unauthorized if not,
and add tests for unauthorized delete attempts; on the frontend, replace the
window.confirm() flow inside handleDeleteBlog with the app's Dialog-based
confirmation (the same Dialog pattern used for the "Create Blog" dialog in
Blogs.jsx) so deletion uses a consistent modal confirmation UI and then call the
delete API only after positive confirmation.

In `@app/src/components/CodeforcesTable.jsx`:
- Around line 289-291: The effect that calls getcffriends() uses an empty
dependency array so it won't re-run when authentication changes; update the
useEffect that currently calls getcffriends() to include isAuthenticated in its
dependency array and ensure getcffriends is stable (either memoize it with
useCallback or move the fetch logic inline inside the useEffect) so friends are
fetched when isAuthenticated becomes true; reference the existing getcffriends
function and the useEffect that calls it to locate where to change the
dependency array.

In `@app/src/components/Friends.jsx`:
- Around line 48-60: The current loadAllFriends function runs fetchFriends
sequentially by awaiting each call in a for loop; replace that with parallel
execution using Promise.all to invoke fetchFriends for every PLATFORMS item
(e.g., map PLATFORMS to platform.id and call fetchFriends) so all requests run
concurrently, and keep the setLoading(true) before launching Promise.all and
setLoading(false) after awaiting Promise.all to preserve loading state; update
the useEffect block that calls loadAllFriends to use the new parallel
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f423dd2-a9fb-4281-bbfa-6979734e7e81

📥 Commits

Reviewing files that changed from the base of the PR and between c179d0f and db9236b.

📒 Files selected for processing (13)
  • api/leaderboard/celery.py
  • api/leaderboard/friends.py
  • api/leaderboard/management/commands/update_db.py
  • api/leaderboard/models.py
  • api/leaderboard/root.py
  • api/leaderboard/serializers.py
  • api/leaderboard/urls.py
  • api/leaderboard/views.py
  • app/src/App.jsx
  • app/src/components/Blogs.jsx
  • app/src/components/CodeforcesTable.jsx
  • app/src/components/Friends.jsx
  • app/src/components/Navbar.jsx

Comment thread api/leaderboard/celery.py Outdated
Comment thread api/leaderboard/celery.py Outdated
Comment thread api/leaderboard/friends.py Outdated
Comment thread api/leaderboard/friends.py Outdated
Comment thread api/leaderboard/management/commands/update_db.py
Comment thread app/src/components/Blogs.jsx
Comment thread app/src/components/Friends.jsx
Comment on lines +32 to +38
try {
const authTokens = JSON.parse(localStorage.getItem("authTokens"));
const response = await fetch(`${BACKEND}/${platformId}${platform.suffix}L/`, {
headers: {
Authorization: `Bearer ${authTokens.access}`,
},
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unguarded JSON.parse on localStorage can crash if data is corrupted.

If localStorage.getItem("authTokens") returns null or invalid JSON, JSON.parse will throw. This pattern appears in multiple places (lines 33, 70, 98).

🛡️ Proposed defensive fix
   try {
-      const authTokens = JSON.parse(localStorage.getItem("authTokens"));
+      const stored = localStorage.getItem("authTokens");
+      if (!stored) return;
+      const authTokens = JSON.parse(stored);
       const response = await fetch(`${BACKEND}/${platformId}${platform.suffix}L/`, {

Consider extracting a helper function to safely retrieve auth tokens:

const getAuthTokens = () => {
  try {
    const stored = localStorage.getItem("authTokens");
    return stored ? JSON.parse(stored) : null;
  } catch {
    return null;
  }
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Friends.jsx` around lines 32 - 38, The code currently
calls JSON.parse(localStorage.getItem("authTokens")) directly (in the fetch
block and at other sites) which can throw on null or corrupted data; add a small
safe helper function (e.g., getAuthTokens) that wraps localStorage.getItem in a
try/catch and returns null on errors, then replace direct JSON.parse calls with
getAuthTokens() and guard uses of authTokens (e.g., Authorization header
creation in the fetch call and any access to authTokens.access) so you only set
the header or proceed when getAuthTokens() returns a non-null object; update all
occurrences referenced in this file (the fetch block and the other spots around
lines with JSON.parse) to use this helper.

Comment on lines +204 to +206
<Avatar className="h-10 w-10">
<AvatarFallback>{handle[0].toUpperCase()}</AvatarFallback>
</Avatar>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential crash if handle is an empty string.

handle[0].toUpperCase() will throw if handle is an empty string (accessing index 0 of empty string returns undefined, and calling toUpperCase() on undefined throws).

🛡️ Proposed fix
-                          <AvatarFallback>{handle[0].toUpperCase()}</AvatarFallback>
+                          <AvatarFallback>{handle?.[0]?.toUpperCase() || "?"}</AvatarFallback>
📝 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
<Avatar className="h-10 w-10">
<AvatarFallback>{handle[0].toUpperCase()}</AvatarFallback>
</Avatar>
<Avatar className="h-10 w-10">
<AvatarFallback>{handle?.[0]?.toUpperCase() || "?"}</AvatarFallback>
</Avatar>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Friends.jsx` around lines 204 - 206, AvatarFallback
currently uses handle[0].toUpperCase() which will throw if handle is an empty
string or falsy; change the rendering to safely derive the initial by checking
handle (e.g., const initial = handle && handle.length ? handle[0].toUpperCase()
: '?' ) and use that initial inside AvatarFallback (update the code around
Avatar / AvatarFallback to reference the new safe initial), ensuring you also
handle null/undefined by falling back to a default character.

Comment on lines +118 to +120
{items.map((item, index) => {
const isActive = pathname === item.url;
const isBlogs = index === 7;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Only one nav item should become active for /.

Line 119 compares only the URL, so Dashboard, Leaderboards, and Analytics all become active on / because each item still points there. Give the placeholder entries distinct routes, or special-case the home row so only one item gets the active indicator.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Navbar.jsx` around lines 118 - 120, The active-state logic
marks multiple items active on "/" because isActive is set by a simple pathname
=== item.url; update the logic inside items.map (where isActive and isBlogs are
computed) so only one item becomes active for the root: either give placeholder
entries distinct routes (change their item.url values from "/" to unique routes)
or special-case the home path by changing the isActive condition to something
like pathname === item.url && !(pathname === "/" && index !==
<desiredHomeIndex>) so only the intended index (e.g., Dashboard) is active when
pathname is "/". Ensure you update references within the same map block
(isActive, item.url, index) and remove the ambiguous placeholders if you choose
to assign unique routes.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
app/src/components/Blogs.jsx (2)

48-58: ⚠️ Potential issue | 🟡 Minor

Guard against missing auth tokens in authenticated actions.

Unlike fetchBlogs which safely checks authTokens?.access before adding the header, handleCreateBlog directly accesses authTokens.access. If an unauthenticated user somehow triggers this (the "Write Blog" button is always visible), this will either throw a TypeError or send a malformed "Bearer undefined" header.

Proposed fix: add early return or disable the submit action for unauthenticated users
 const handleCreateBlog = async () => {
   if (!title.trim() || !description.trim()) return;
+  if (!authTokens?.access) {
+    console.error("Authentication required to create a blog");
+    return;
+  }
   
   setLoading(true);
   try {

Alternatively, disable the dialog trigger for unauthenticated users:

-<DialogTrigger asChild>
-  <Button className="gap-2">
+<DialogTrigger asChild disabled={!authTokens?.access}>
+  <Button className="gap-2" disabled={!authTokens?.access}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 48 - 58, handleCreateBlog
currently accesses authTokens.access directly and can send "Bearer undefined" or
throw; update handleCreateBlog to guard using authTokens?.access (or check if
authTokens && authTokens.access) and early-return with a user-facing error or
prevent submission if missing. Alternatively, disable the "Write Blog"
dialog/submit when authTokens?.access is falsy; ensure the Authorization header
is only added when authTokens?.access exists and mirror the safe pattern used in
fetchBlogs to avoid TypeError or malformed headers.

90-101: ⚠️ Potential issue | 🟡 Minor

Guard against missing auth tokens in like/dislike interactions.

Same issue as handleCreateBlog: the like/dislike buttons are visible to all users, but handleInteract assumes authTokens.access exists. Unauthenticated users clicking these will cause errors or send malformed headers.

Proposed fix: add auth guard
 const handleInteract = async (post, action) => {
+  if (!authTokens?.access) {
+    console.error("Authentication required to interact");
+    return;
+  }
   let updatedLikes = post.likes;
   let updatedDislikes = post.dislikes;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 90 - 101, The like/dislike handler
currently assumes authTokens.access exists (seen in the fetch call inside
handleInteract), which will break for unauthenticated users; update
handleInteract to first check authTokens and authTokens.access and early-return
(or show a login prompt/toast) if missing, preventing the fetch to
`${BACKEND}/discussionpost/` and avoiding malformed Authorization headers;
ensure the same guard is applied before making the PUT request and before any UI
state that assumes auth (use the symbols authTokens.access, handleInteract, and
BACKEND to locate the change).
api/leaderboard/management/commands/update_db.py (1)

166-183: ⚠️ Potential issue | 🟡 Minor

Missing timeout on nested API request.

Line 171 makes a requests.get() call to the GitHub API without a timeout. While the initial page fetch (line 159) now has a timeout, this nested call could hang indefinitely and block the entire update process.

🔧 Suggested fix
                 url = f"https://api.github.com/users/{gh_user.username}/repos"
-                response = requests.get(url).json()
+                response = requests.get(url, headers=headers, timeout=10).json()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/management/commands/update_db.py` around lines 166 - 183, The
nested GitHub API call using requests.get(url) (where response =
requests.get(url).json() and later used to compute repositories/stars for
gh_user) lacks a timeout and can hang; update that call to include a sensible
timeout (e.g. timeout=5) and wrap it in a try/except for
requests.exceptions.RequestException so you can fallback (e.g. treat response as
[] or log the error) before computing repositories/stars and appending instance
to updates to avoid blocking the update_db workflow.
api/leaderboard/views.py (2)

610-628: ⚠️ Potential issue | 🟠 Major

PUT allows any authenticated user to edit any post's content.

The PUT method only looks up the post by title without verifying ownership. While the comments note this allows anyone to like/dislike, it also permits modifying title and discription of posts authored by others. Consider separating like/dislike updates from content editing, or adding ownership checks for content fields.

🔧 Proposed fix - restrict content editing to author
     def put(self, request):
         try:
             post = DiscussionPost.objects.get(title=request.data["title"])
         except DiscussionPost.DoesNotExist:
             return Response(
                 {"error": "Post not found"}, status=status.HTTP_404_NOT_FOUND
             )

-        for field in ["title", "discription", "likes", "dislikes", "comments"]:
+        # Anyone can update likes/dislikes/comments
+        for field in ["likes", "dislikes", "comments"]:
+            if field in request.data:
+                setattr(post, field, request.data[field])
+
+        # Only author can edit content
+        if post.username == request.user:
+            for field in ["title", "discription"]:
+                if field in request.data:
+                    setattr(post, field, request.data[field])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 610 - 628, The PUT handler in
put(self, request) fetches DiscussionPost by title but doesn't restrict edits to
content fields; update it so ownership is enforced: before applying updates,
check if any content fields ("title", "discription") are present in request.data
and if so verify post.author == request.user (or return a 403 Response if not);
allow non-authors to modify only non-content fields ("likes", "dislikes",
"comments"); keep the current save/serialize flow after the checks.

722-728: ⚠️ Potential issue | 🔴 Critical

Field name mismatch: description vs discription.

The loop iterates over "description" but the ReplyPost model (per models.py line 165) uses the misspelled field "discription". The setattr will create a new attribute instead of updating the model field, causing content updates to silently fail.

🐛 Proposed fix
-        for field in ["description", "likes", "dislikes"]:
+        for field in ["discription", "likes", "dislikes"]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 722 - 728, The loop in the view that
updates reply fields uses the correct intent "description" but the Reply model
actually defines the misspelled field "discription", so setattr creates a new
attribute instead of updating the stored field; update the view to set the
actual model field name (use "discription" instead of "description") when
iterating over allowed fields (or accept both keys by mapping "description" ->
"discription"), ensuring the Reply instance is modified before reply.save() and
serialized via ReplyPost_Serializer(reply).
♻️ Duplicate comments (3)
app/src/components/Blogs.jsx (1)

112-135: ⚠️ Potential issue | 🟡 Minor

Add auth guard and consider using blog ID for deletion.

  1. Auth guard: For consistency with fetchBlogs and safety, add a null check for authTokens?.access.

  2. Title-based identification: Deleting by title (line 123) could be fragile if duplicate titles exist. Using post.id would be more reliable if the backend supports it.

Proposed fix
 const handleDeleteBlog = async (post) => {
   if (!window.confirm("Are you sure you want to delete this blog?")) return;
+  if (!authTokens?.access) {
+    console.error("Authentication required to delete a blog");
+    return;
+  }

   try {
     const response = await fetch(`${BACKEND}/discussionpost/`, {
       method: "DELETE",
       headers: {
         "Content-Type": "application/json",
         "Authorization": `Bearer ${authTokens.access}`,
       },
       body: JSON.stringify({
-        title: post.title,
+        id: post.id,  // More reliable than title if backend supports it
       }),
     });

User feedback on delete failure (from past review).

The past review suggested adding user-facing feedback (e.g., alert()) when deletion fails. Currently, only console.error is used on lines 130 and 133, which users won't see.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 112 - 135, handleDeleteBlog should
guard against missing auth and use a stable identifier for deletion: check
authTokens?.access before making the request (same pattern as fetchBlogs) and
prefer sending post.id (or include the id in the DELETE URL like
`${BACKEND}/discussionpost/${post.id}`) instead of title to avoid collisions;
also replace or complement console.error calls in handleDeleteBlog with
user-facing feedback (e.g., alert or toast) on both non-ok responses and caught
errors so the user is informed when deletion fails.
app/src/components/Friends.jsx (2)

205-207: ⚠️ Potential issue | 🟡 Minor

Potential crash if handle is an empty string.

handle[0].toUpperCase() will throw TypeError when handle is an empty string since ""[0] is undefined.

🛡️ Proposed fix
-                          <AvatarFallback>{handle[0].toUpperCase()}</AvatarFallback>
+                          <AvatarFallback>{handle?.[0]?.toUpperCase() || "?"}</AvatarFallback>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Friends.jsx` around lines 205 - 207, The AvatarFallback
usage can crash when handle is an empty string because handle[0].toUpperCase()
will access undefined; update the Friends component to guard and compute a safe
fallback before rendering (e.g., derive a variable like avatarInitial = handle
&& handle.length > 0 ? handle[0].toUpperCase() : '?' ) and pass that to
<AvatarFallback> (refer to AvatarFallback and the handle variable in
Friends.jsx); ensure the logic handles null/undefined too so AvatarFallback
always receives a string.

33-39: ⚠️ Potential issue | 🟡 Minor

Unguarded JSON.parse can crash on null or corrupted data.

localStorage.getItem("authTokens") may return null or corrupted JSON, causing JSON.parse to throw. This pattern appears at lines 34, 71, and 99.

🛡️ Proposed fix - add a helper function
+const getAuthTokens = () => {
+  try {
+    const stored = localStorage.getItem("authTokens");
+    return stored ? JSON.parse(stored) : null;
+  } catch {
+    return null;
+  }
+};
+
 const fetchFriends = async (platformId) => {
   const platform = PLATFORMS.find(p => p.id === platformId);
   if (!platform) return;

   try {
-    const authTokens = JSON.parse(localStorage.getItem("authTokens"));
+    const authTokens = getAuthTokens();
+    if (!authTokens?.access) return;
     const response = await fetch(`${BACKEND}/${platformId}${platform.suffix}L/`, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Friends.jsx` around lines 33 - 39, Replace the unguarded
JSON.parse(localStorage.getItem("authTokens")) calls with a small helper (e.g.,
getStoredAuthTokens or parseAuthTokens) that reads localStorage, wraps
JSON.parse in try/catch, returns null on failure, and logs or handles errors;
then update the fetch callers in Friends.jsx (the try block fetching
`${BACKEND}/${platformId}${platform.suffix}L/` and the other usages around lines
71 and 99) to call this helper and guard access to authTokens.access (early
return or refresh/unauth flow) so you never dereference access on a null/invalid
token object.
🧹 Nitpick comments (5)
api/leaderboard/management/commands/update_db.py (2)

270-275: Consider adding timeouts for consistency.

The PR adds timeouts to CodeChef, GitHub (partial), LeetCode, and AtCoder requests, but openlake_contributor_update still has requests without timeouts at lines 270 and 275. For consistent behavior and to prevent hangs, consider adding timeouts here as well.

♻️ Suggested fix
-        response = requests.get(url, headers=headers).json()
+        response = requests.get(url, headers=headers, timeout=10).json()

         for i in range(len(response)):
             repo_url = str(response[i]["contributors_url"])
             try:
-                repo_response = requests.get(repo_url).json()
+                repo_response = requests.get(repo_url, headers=headers, timeout=10).json()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/management/commands/update_db.py` around lines 270 - 275, The
requests in openlake_contributor_update currently call requests.get(...).json()
without timeouts (specifically the call that assigns response =
requests.get(url, headers=headers).json() and the repo_response =
requests.get(repo_url).json()), which can hang; update both calls to pass a
timeout (e.g., timeout=5 or a project-constant like HTTP_TIMEOUT) to
requests.get and handle potential requests.exceptions.Timeout/RequestException
where appropriate in openlake_contributor_update to maintain consistency with
other providers.

300-301: Move import re to module level.

Importing inside the function adds overhead on each call and deviates from Python conventions. Move this import to the top of the file with other imports.

♻️ Suggested fix

At the top of the file (after line 6):

import re

Then remove line 301.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/management/commands/update_db.py` around lines 300 - 301, The
import re is inside the atcoder_user_update function which causes repeated
import overhead and violates convention; move the import to the module level
with the other imports (add a single top-of-file import for re) and remove the
in-function statement from atcoder_user_update so the function uses the
module-level re.
app/src/components/Friends.jsx (1)

49-61: Consider fetching friends in parallel for faster loading.

The sequential await fetchFriends(platform.id) in a loop makes 6 requests serially. Using Promise.all would load all platforms concurrently.

♻️ Optional refactor for parallel fetching
   useEffect(() => {
     const loadAllFriends = async () => {
       setLoading(true);
-      for (const platform of PLATFORMS) {
-        await fetchFriends(platform.id);
-      }
+      await Promise.all(PLATFORMS.map(platform => fetchFriends(platform.id)));
       setLoading(false);
     };

     if (user) {
       loadAllFriends();
     }
   }, [user]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Friends.jsx` around lines 49 - 61, The current
loadAllFriends function performs sequential requests by awaiting
fetchFriends(platform.id) inside a for loop, slowing loading; change it to run
all fetches concurrently using Promise.all over PLATFORMS.map(p =>
fetchFriends(p.id)) and await that single Promise, while preserving
setLoading(true) before and setLoading(false) after; update loadAllFriends and
its use in the useEffect (references: loadAllFriends, fetchFriends, PLATFORMS,
setLoading, useEffect) so all platform fetches run in parallel and errors are
still handled appropriately.
api/leaderboard/views.py (1)

761-784: Add validation for slug and tier values.

The endpoint accepts any arbitrary strings for slug and tier without validating against a defined set of valid achievements. The model fields have no constraints beyond max_length=64 and no validators are applied. This allows clients to create invalid or unintended achievement records. If achievements are predefined or have specific valid values, add validation to restrict accepted values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 761 - 784, The endpoint currently
allows any slug/tier strings; update AchievementUnlock.post to validate the
incoming slug and tier against the canonical set of allowed achievements before
creating records: either query the authoritative source (e.g., an
AchievementDefinition model or a module-level VALID_ACHIEVEMENTS mapping) to
confirm the slug exists and that the tier is among the allowed tiers for that
slug, and return a 400 Response with an explanatory error if not valid;
alternatively, move validation into an AchievementUnlockSerializer that performs
the same checks and use serializer.is_valid() before calling
Achievement.objects.get_or_create, referencing the Achievement model,
AchievementUnlock view, and AchievementSerializer to locate where to apply this
validation.
api/leaderboard/celery.py (1)

220-221: Remove the redundant manual assignment and let Django manage last_updated.

The githubUser.last_updated field is defined with auto_now=True in api/leaderboard/models.py (line 17), which automatically updates it to the current datetime on every save(). The manual assignment at line 220 is overwritten immediately and represents a type mismatch (float vs DateTimeField). Removing it clarifies field ownership.

Suggested cleanup
-            gh_user.last_updated = datetime.now().timestamp()
             gh_user.save()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/celery.py` around lines 220 - 221, Remove the redundant
manual assignment to gh_user.last_updated before saving: the githubUser model's
last_updated uses auto_now=True so Django will set it on gh_user.save(); delete
the line that sets gh_user.last_updated = datetime.now().timestamp() and leave
gh_user.save() unchanged to avoid the float/DateTimeField mismatch and let
Django manage the timestamp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/leaderboard/celery.py`:
- Around line 180-209: fetch_github_contributions currently returns 0 on any
non-200 response and fetch_repo_stars may return a partial total if a later
paged request fails; change both functions (fetch_github_contributions and
fetch_repo_stars) to return None (or raise a clear exception) when an upstream
request fails so callers can detect failed fetches and avoid persisting
zero/partial values, and update the caller logic that saves metrics to skip
persistence when these functions return None; for fetch_repo_stars specifically,
if any page request returns non-200 or parsing of the Link header fails, abort
and return None instead of the accumulated total.

In `@api/leaderboard/friends.py`:
- Line 59: Wrap DRF validation errors into the JSON shape the frontend expects
by transforming serializer.errors into a single "message" string before
returning; update the return that currently does Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST) to instead extract the first field error
(e.g., next(iter(serializer.errors.values()))[0] or join messages) and return
Response({"message": "<extracted message>"}, status=status.HTTP_400_BAD_REQUEST)
so the frontend (Friends.jsx) can read errorData.message consistently.

---

Outside diff comments:
In `@api/leaderboard/management/commands/update_db.py`:
- Around line 166-183: The nested GitHub API call using requests.get(url) (where
response = requests.get(url).json() and later used to compute repositories/stars
for gh_user) lacks a timeout and can hang; update that call to include a
sensible timeout (e.g. timeout=5) and wrap it in a try/except for
requests.exceptions.RequestException so you can fallback (e.g. treat response as
[] or log the error) before computing repositories/stars and appending instance
to updates to avoid blocking the update_db workflow.

In `@api/leaderboard/views.py`:
- Around line 610-628: The PUT handler in put(self, request) fetches
DiscussionPost by title but doesn't restrict edits to content fields; update it
so ownership is enforced: before applying updates, check if any content fields
("title", "discription") are present in request.data and if so verify
post.author == request.user (or return a 403 Response if not); allow non-authors
to modify only non-content fields ("likes", "dislikes", "comments"); keep the
current save/serialize flow after the checks.
- Around line 722-728: The loop in the view that updates reply fields uses the
correct intent "description" but the Reply model actually defines the misspelled
field "discription", so setattr creates a new attribute instead of updating the
stored field; update the view to set the actual model field name (use
"discription" instead of "description") when iterating over allowed fields (or
accept both keys by mapping "description" -> "discription"), ensuring the Reply
instance is modified before reply.save() and serialized via
ReplyPost_Serializer(reply).

In `@app/src/components/Blogs.jsx`:
- Around line 48-58: handleCreateBlog currently accesses authTokens.access
directly and can send "Bearer undefined" or throw; update handleCreateBlog to
guard using authTokens?.access (or check if authTokens && authTokens.access) and
early-return with a user-facing error or prevent submission if missing.
Alternatively, disable the "Write Blog" dialog/submit when authTokens?.access is
falsy; ensure the Authorization header is only added when authTokens?.access
exists and mirror the safe pattern used in fetchBlogs to avoid TypeError or
malformed headers.
- Around line 90-101: The like/dislike handler currently assumes
authTokens.access exists (seen in the fetch call inside handleInteract), which
will break for unauthenticated users; update handleInteract to first check
authTokens and authTokens.access and early-return (or show a login prompt/toast)
if missing, preventing the fetch to `${BACKEND}/discussionpost/` and avoiding
malformed Authorization headers; ensure the same guard is applied before making
the PUT request and before any UI state that assumes auth (use the symbols
authTokens.access, handleInteract, and BACKEND to locate the change).

---

Duplicate comments:
In `@app/src/components/Blogs.jsx`:
- Around line 112-135: handleDeleteBlog should guard against missing auth and
use a stable identifier for deletion: check authTokens?.access before making the
request (same pattern as fetchBlogs) and prefer sending post.id (or include the
id in the DELETE URL like `${BACKEND}/discussionpost/${post.id}`) instead of
title to avoid collisions; also replace or complement console.error calls in
handleDeleteBlog with user-facing feedback (e.g., alert or toast) on both non-ok
responses and caught errors so the user is informed when deletion fails.

In `@app/src/components/Friends.jsx`:
- Around line 205-207: The AvatarFallback usage can crash when handle is an
empty string because handle[0].toUpperCase() will access undefined; update the
Friends component to guard and compute a safe fallback before rendering (e.g.,
derive a variable like avatarInitial = handle && handle.length > 0 ?
handle[0].toUpperCase() : '?' ) and pass that to <AvatarFallback> (refer to
AvatarFallback and the handle variable in Friends.jsx); ensure the logic handles
null/undefined too so AvatarFallback always receives a string.
- Around line 33-39: Replace the unguarded
JSON.parse(localStorage.getItem("authTokens")) calls with a small helper (e.g.,
getStoredAuthTokens or parseAuthTokens) that reads localStorage, wraps
JSON.parse in try/catch, returns null on failure, and logs or handles errors;
then update the fetch callers in Friends.jsx (the try block fetching
`${BACKEND}/${platformId}${platform.suffix}L/` and the other usages around lines
71 and 99) to call this helper and guard access to authTokens.access (early
return or refresh/unauth flow) so you never dereference access on a null/invalid
token object.

---

Nitpick comments:
In `@api/leaderboard/celery.py`:
- Around line 220-221: Remove the redundant manual assignment to
gh_user.last_updated before saving: the githubUser model's last_updated uses
auto_now=True so Django will set it on gh_user.save(); delete the line that sets
gh_user.last_updated = datetime.now().timestamp() and leave gh_user.save()
unchanged to avoid the float/DateTimeField mismatch and let Django manage the
timestamp.

In `@api/leaderboard/management/commands/update_db.py`:
- Around line 270-275: The requests in openlake_contributor_update currently
call requests.get(...).json() without timeouts (specifically the call that
assigns response = requests.get(url, headers=headers).json() and the
repo_response = requests.get(repo_url).json()), which can hang; update both
calls to pass a timeout (e.g., timeout=5 or a project-constant like
HTTP_TIMEOUT) to requests.get and handle potential
requests.exceptions.Timeout/RequestException where appropriate in
openlake_contributor_update to maintain consistency with other providers.
- Around line 300-301: The import re is inside the atcoder_user_update function
which causes repeated import overhead and violates convention; move the import
to the module level with the other imports (add a single top-of-file import for
re) and remove the in-function statement from atcoder_user_update so the
function uses the module-level re.

In `@api/leaderboard/views.py`:
- Around line 761-784: The endpoint currently allows any slug/tier strings;
update AchievementUnlock.post to validate the incoming slug and tier against the
canonical set of allowed achievements before creating records: either query the
authoritative source (e.g., an AchievementDefinition model or a module-level
VALID_ACHIEVEMENTS mapping) to confirm the slug exists and that the tier is
among the allowed tiers for that slug, and return a 400 Response with an
explanatory error if not valid; alternatively, move validation into an
AchievementUnlockSerializer that performs the same checks and use
serializer.is_valid() before calling Achievement.objects.get_or_create,
referencing the Achievement model, AchievementUnlock view, and
AchievementSerializer to locate where to apply this validation.

In `@app/src/components/Friends.jsx`:
- Around line 49-61: The current loadAllFriends function performs sequential
requests by awaiting fetchFriends(platform.id) inside a for loop, slowing
loading; change it to run all fetches concurrently using Promise.all over
PLATFORMS.map(p => fetchFriends(p.id)) and await that single Promise, while
preserving setLoading(true) before and setLoading(false) after; update
loadAllFriends and its use in the useEffect (references: loadAllFriends,
fetchFriends, PLATFORMS, setLoading, useEffect) so all platform fetches run in
parallel and errors are still handled appropriately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3b2b340-4720-4419-9553-617e4af10c81

📥 Commits

Reviewing files that changed from the base of the PR and between db9236b and ebaed51.

📒 Files selected for processing (7)
  • api/leaderboard/celery.py
  • api/leaderboard/friends.py
  • api/leaderboard/management/commands/update_db.py
  • api/leaderboard/migrations/0013_achievement.py
  • api/leaderboard/views.py
  • app/src/components/Blogs.jsx
  • app/src/components/Friends.jsx

Comment thread api/leaderboard/celery.py
Comment thread api/leaderboard/friends.py Outdated
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
api/leaderboard/views.py (1)

753-774: ⚠️ Potential issue | 🟡 Minor

DiscussionReplyManage.put assumes one reply per user per post.

The query ReplyPost.objects.get(username=request.user, parent=post) will raise MultipleObjectsReturned if a user has posted multiple replies to the same discussion. Consider using filter().first() or requiring a reply ID.

🔧 Proposed fix - require reply ID
     def put(self, request):
+        reply_id = request.data.get("reply_id")
+        if not reply_id:
+            return Response(
+                {"error": "reply_id is required"}, status=status.HTTP_400_BAD_REQUEST
+            )
+
         try:
-            post = DiscussionPost.objects.get(title=request.data["title"])
-        except DiscussionPost.DoesNotExist:
+            reply = ReplyPost.objects.get(id=reply_id, username=request.user)
+        except ReplyPost.DoesNotExist:
             return Response(
-                {"error": "Post not found"}, status=status.HTTP_404_NOT_FOUND
+                {"error": "Reply not found or not owned by you"}, status=status.HTTP_404_NOT_FOUND
             )
-
-        try:
-            reply = ReplyPost.objects.get(username=request.user, parent=post)
-        except ReplyPost.DoesNotExist:
-            return Response(
-                {"error": "Reply not found"}, status=status.HTTP_404_NOT_FOUND
-            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 753 - 774, The put handler
DiscussionReplyManage.put currently assumes a single reply per user per post by
calling ReplyPost.objects.get(username=request.user, parent=post), which can
raise MultipleObjectsReturned; require a reply identifier instead and fetch by
it: read reply_id from request.data (e.g., "reply_id"), then call
ReplyPost.objects.get(id=reply_id, username=request.user, parent=post) and
handle ReplyPost.DoesNotExist returning 404; alternatively, if you prefer not to
require an ID, replace the get with
ReplyPost.objects.filter(username=request.user, parent=post).first() and return
404 when the result is None—ensure you update validation and error messages
accordingly.
api/leaderboard/api/views.py (1)

196-204: ⚠️ Potential issue | 🟡 Minor

Missing AtCoder username support in registerUser.

The registerUser function handles cc_uname, cf_uname, gh_uname, and lt_uname, but does not extract or persist ac_uname. This creates an inconsistency with post_UserNames which does support AtCoder, and means users registering with an AtCoder username won't have it saved.

🔧 Proposed fix
         lt_uname = request.data.get("lt_uname", "")
+        ac_uname = request.data.get("ac_uname", "")
 
         if not all([first_name, email, username]):

And update the UserNames creation:

         userName = UserNames(
             user=user,
             cc_uname=cc_uname,
             cf_uname=cf_uname,
             gh_uname=gh_uname,
             lt_uname=lt_uname,
+            ac_uname=ac_uname,
         )

And add AtcoderUser creation:

         if lt_uname:
             lt_user = LeetcodeUser(username=lt_uname)
             lt_user.save()
             logger.info(f"LeetCode username {lt_uname} saved")
+        if ac_uname:
+            ac_user = AtcoderUser(username=ac_uname)
+            ac_user.save()
+            logger.info(f"AtCoder username {ac_uname} saved")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/api/views.py` around lines 196 - 204, registerUser currently
collects and saves cc_uname, cf_uname, gh_uname, lt_uname into the UserNames
model but omits the AtCoder username; update the request parsing in registerUser
to also extract ac_uname, include ac_uname when constructing/saving the
UserNames instance (UserNames(..., ac_uname=ac_uname)), and, where other
per-platform user records are created (e.g.,
CodeChefUser/CodeForcesUser/LeetCodeUser creation logic), add creation of the
AtcoderUser record linked to the user using ac_uname so AtCoder usernames are
persisted consistently with post_UserNames.
app/src/components/Blogs.jsx (1)

39-41: ⚠️ Potential issue | 🟡 Minor

Missing authTokens in useEffect dependency array.

The fetchBlogs function uses authTokens?.access to conditionally send the Authorization header, but authTokens is not listed in the dependency array. This means fetchBlogs won't re-run when the user logs in or out, potentially showing stale user_vote data.

Proposed fix
   useEffect(() => {
     fetchBlogs();
-  }, []);
+  }, [authTokens?.access]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 39 - 41, The useEffect currently
calls fetchBlogs() with an empty dependency array, but fetchBlogs reads
authTokens?.access so add authTokens to the dependency array of the useEffect
that invokes fetchBlogs (i.e., update the effect that contains useEffect(() => {
fetchBlogs(); }, [])) so it re-runs when authTokens changes; ensure fetchBlogs
is stable (memoize with useCallback if necessary) or include its reference in
the dependency list to avoid stale closures.
app/src/components/Achievements.jsx (2)

16-61: ⚠️ Potential issue | 🟡 Minor

Missing authTokens in useEffect dependency array causes stale closure.

The effect uses authTokens.access in both fetchAllStats and fetchUnlocked, but only [user, BACKEND] are in the dependency array. This causes:

  1. ESLint exhaustive-deps warning
  2. If tokens refresh while the component is mounted, requests will use stale tokens
  3. On initial mount, authTokens may be undefined briefly, causing the request to fail
Proposed fix
     fetchAllStats();
     fetchUnlocked();
-  }, [user, BACKEND]);
+  }, [user, BACKEND, authTokens?.access]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Achievements.jsx` around lines 16 - 61, The useEffect
closure reads authTokens.access in fetchAllStats and fetchUnlocked but the
dependency array only lists [user, BACKEND], causing stale tokens and an ESLint
exhaustive-deps warning; update the effect dependencies to include authTokens
(or authTokens.access) and any setters used, and ensure fetchAllStats and
fetchUnlocked use the latest authTokens by referencing the same variable inside
the effect (functions: fetchAllStats, fetchUnlocked) or memoizing tokens before
the calls; also guard early when authTokens or authTokens.access is falsy to
avoid firing requests with undefined tokens and maintain
setIsLoading/setUnlockedAchievements behavior.

64-102: ⚠️ Potential issue | 🟠 Major

Achievement unlock effect inefficiently re-evaluates all achievements when one unlocks.

This effect includes unlockedAchievements in its dependency array, causing it to re-run whenever an achievement is unlocked. While the hasIt check prevents duplicate API calls for the same achievement, the effect still re-evaluates all achievements on every unlock, which is inefficient. Additionally, multiple unlockAchievement calls can be in flight simultaneously, creating potential race conditions where concurrent requests update state unpredictably.

Remove unlockedAchievements from the dependency array:

-  }, [stats, user, BACKEND, globalStreak, unlockedAchievements]);
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [stats, user, BACKEND, globalStreak]);

The effect should only re-evaluate when stats, user, BACKEND, or streak changes—not when unlocked achievements change. The initial check ensures new unlocks are properly detected without re-running when the unlock list updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Achievements.jsx` around lines 64 - 102, The effect
re-runs on every unlockedAchievements change causing unnecessary full
re-evaluations and possible race conditions; remove unlockedAchievements from
the useEffect dependency array so the effect only runs on stats, user, BACKEND,
or globalStreak changes, and prevent concurrent duplicate unlock requests by
adding an in-flight tracker (e.g., a useRef Set called inflightUnlocks) that
unlockAchievement checks/updates before sending a POST; keep
setUnlockedAchievements using the functional updater
(setUnlockedAchievements(prev => [...prev, newUnlock])) as used to avoid lost
updates from concurrent responses.
🧹 Nitpick comments (7)
api/leaderboard/views.py (3)

349-360: Use timezone-aware datetime for consistency.

datetime.now() returns a naive datetime without timezone info. The model's last_updated field uses auto_now=True which stores timezone-aware datetimes. Comparing naive and aware datetimes can cause subtle bugs. Use Django's timezone-aware now() instead.

♻️ Proposed fix

Add to imports:

+from django.utils.timezone import now

Then update the filter:

-        for user in cc_users.filter(last_updated__lt=datetime.now() - timedelta(minutes=15))[:5]:
+        for user in cc_users.filter(last_updated__lt=now() - timedelta(minutes=15))[:5]:

And update line 359:

-                user.last_updated = datetime.now()
+                user.last_updated = now()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 349 - 360, The code is comparing naive
datetimes; replace uses of datetime.now() with Django's timezone-aware now():
import django.utils.timezone.now and update the cc_users.filter(...) condition
and the assignment user.last_updated = ... to use timezone.now() instead of
datetime.now(); keep timedelta usage as-is and leave get_codechef_data,
user.rating, user.save() logic unchanged.

696-696: Remove duplicate import.

IsAuthenticatedOrReadOnly is already imported at line 13.

♻️ Proposed fix
-from rest_framework.permissions import IsAuthenticatedOrReadOnly
-
 class AtcoderViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` at line 696, Remove the duplicate import of
IsAuthenticatedOrReadOnly from rest_framework.permissions in the file; locate
the redundant import statement (the one shown at the top of the snippet) and
delete it so only the original import (already present near the top of the
module) remains, ensuring no other imports on that line are affected.

593-608: N+1 query problem when annotating user votes.

This loop queries PostVote for each post individually, causing N+1 database queries. For a page with many posts, this will significantly impact performance.

♻️ Proposed fix using a single query
     def get(self, request):
         posts = DiscussionPost.objects.all()
-        data = []
-        for post in posts:
-            post_data = DiscussionPost_Serializer(post).data
-            # Annotate with the authenticated user's current vote
-            if request.user.is_authenticated:
-                try:
-                    vote = PostVote.objects.get(user=request.user, post=post)
-                    post_data["user_vote"] = vote.vote_type
-                except PostVote.DoesNotExist:
-                    post_data["user_vote"] = None
-            else:
-                post_data["user_vote"] = None
-            data.append(post_data)
-        return Response(data, status=status.HTTP_200_OK)
+        serialized = DiscussionPost_Serializer(posts, many=True).data
+        
+        if request.user.is_authenticated:
+            # Fetch all votes for this user in one query
+            user_votes = {
+                v.post_id: v.vote_type
+                for v in PostVote.objects.filter(user=request.user, post__in=posts)
+            }
+            for post_data in serialized:
+                post_data["user_vote"] = user_votes.get(post_data["id"])
+        else:
+            for post_data in serialized:
+                post_data["user_vote"] = None
+        
+        return Response(serialized, status=status.HTTP_200_OK)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 593 - 608, The get method on the view
is causing an N+1 query by calling PostVote.objects.get for every
DiscussionPost; fix it by loading all relevant votes in one query when
request.user.is_authenticated (use PostVote.objects.filter(user=request.user,
post__in=posts)), build a mapping from post id to vote_type, then iterate posts
and set post_data["user_vote"] = votes_map.get(post.id, None) before appending;
keep using DiscussionPost_Serializer(post).data and only fall back to per-post
logic when the user is anonymous.
api/leaderboard/users.py (1)

72-77: Avoid bare except and move import to top of file.

The bare except clause catches all exceptions including KeyboardInterrupt and SystemExit, which can mask real issues. Also, the AtcoderUserSerializer import should be at the module level with other serializer imports for consistency and slight performance benefit.

♻️ Proposed fix

Add to imports at line 14-21:

 from leaderboard.serializers import (
     CC_Serializer,
     CF_Serializer,
     GH_Serializer,
     LT_Serializer,
     OL_Serializer,
     UserNamesSerializer,
+    AtcoderUserSerializer,
 )

Then update lines 72-77:

     try:
         atcoderDetails = AtcoderUser.objects.get(username=userDetails["ac_uname"])
-        from leaderboard.serializers import AtcoderUserSerializer
         atcoderDetails = AtcoderUserSerializer(atcoderDetails).data
-    except:
+    except (AtcoderUser.DoesNotExist, KeyError):
         atcoderDetails = {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/users.py` around lines 72 - 77, Replace the bare except and
inline import: move the AtcoderUserSerializer import to the module-level with
other serializers, then wrap the lookup in a try/except that only handles
AtcoderUser.DoesNotExist (i.e. catch that specific exception from
AtcoderUser.objects.get) and set atcoderDetails = {} in that case; do not
silence other exceptions so errors propagate. Ensure you reference
AtcoderUser.objects.get and AtcoderUserSerializer when updating the code.
api/leaderboard/api/views.py (1)

152-156: Remove duplicate import and redundant logging configuration.

The logging module is already imported at line 1, and logger is already initialized at line 29. This duplicate setup is unnecessary and logging.basicConfig() should generally be called once at application startup, not in a module.

♻️ Proposed fix
-import logging
-
-# Configure logging
-logging.basicConfig(level=logging.DEBUG)
-logger = logging.getLogger(__name__)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/api/views.py` around lines 152 - 156, This module contains a
duplicate import and redundant logging setup: remove the second "import
logging", the "logging.basicConfig(...)" call and the duplicate "logger =
logging.getLogger(__name__)" so the module uses the single existing logger
instance already initialized elsewhere (the earlier logger variable). Ensure no
calls to logging.basicConfig remain in this module and that all logging uses the
existing logger symbol.
app/src/components/Blogs.jsx (2)

75-96: Optimistic update logic has edge case issues when swapping votes.

When swapping from like to dislike (or vice versa), the logic for decrementing the opposite counter is correct, but the increment logic doesn't account for the swap scenario properly. When swapping is true:

  • Line 88-90: For action === "like", it correctly adds 1 to likes, but the swapping branch decrements likes by 1 when it should leave it alone (the swap adds +1 to the new action).
  • The ternary nesting makes this hard to follow and may produce incorrect counts in edge cases.

Additionally, Math.max(0, ...) is only applied to the decrement on swap, but not to the alreadyVoted decrement case (lines 89, 92), which could theoretically go negative if state becomes inconsistent.

Clearer optimistic update logic
     setBlogs(prevBlogs => prevBlogs.map(b => {
       if (b.id !== post.id) return b;
       const alreadyVoted = b.user_vote === action;
       const swapping = b.user_vote && b.user_vote !== action;
+      
+      let newLikes = b.likes;
+      let newDislikes = b.dislikes;
+      
+      if (action === "like") {
+        newLikes = alreadyVoted ? Math.max(0, b.likes - 1) : b.likes + 1;
+        if (swapping) newDislikes = Math.max(0, b.dislikes - 1);
+      } else {
+        newDislikes = alreadyVoted ? Math.max(0, b.dislikes - 1) : b.dislikes + 1;
+        if (swapping) newLikes = Math.max(0, b.likes - 1);
+      }
+      
       return {
         ...b,
-        likes: action === "like"
-          ? alreadyVoted ? b.likes - 1 : b.likes + 1
-          : swapping ? Math.max(0, b.likes - 1) : b.likes,
-        dislikes: action === "dislike"
-          ? alreadyVoted ? b.dislikes - 1 : b.dislikes + 1
-          : swapping ? Math.max(0, b.dislikes - 1) : b.dislikes,
+        likes: newLikes,
+        dislikes: newDislikes,
         user_vote: alreadyVoted ? null : action,
       };
     }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 75 - 96, The optimistic update in
handleInteract incorrectly handles swap cases and can produce negative counts;
refactor the setBlogs update for clarity: compute alreadyVoted = b.user_vote ===
action and swapping = b.user_vote && b.user_vote !== action, then update likes
and dislikes with explicit branches that (1) if alreadyVoted decrement the
respective counter using Math.max(0, ...) to avoid negatives and set user_vote
null, (2) if swapping decrement the old counter using Math.max(0, ...) and
increment the new counter, and (3) if neither just increment the targeted
counter; update user_vote to alreadyVoted ? null : action so counts always
reflect the swap correctly in handleInteract and the mapped blog object.

112-114: Silent failure on network errors loses user feedback.

When the catch block runs, it silently refetches without notifying the user that their action may have failed. Combined with the optimistic update, users might see their vote appear, then disappear without explanation.

Consider adding user feedback on error
-    } catch {
-      fetchBlogs();
+    } catch (error) {
+      console.error("Error updating vote:", error);
+      fetchBlogs(); // Revert optimistic update
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Blogs.jsx` around lines 112 - 114, The catch block
currently swallows errors and only calls fetchBlogs(), which hides network
failures and provides no user feedback after an optimistic update; change the
catch to accept the error (catch (err)) and in that handler: log the error, show
a user-facing notification (e.g., call your toast or setError handler)
explaining the vote failed, and revert the optimistic UI by calling fetchBlogs()
(or undo the local state change) so the UI and server stay consistent; update
the catch around the network-vote call (the function invoking fetchBlogs() and
the optimistic update, e.g., voteBlog/handleVote) to implement these steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/leaderboard/users.py`:
- Around line 34-44: The early return inside the UserNames.DoesNotExist
exception handler returns a Response missing the "atcoder" key, causing
inconsistent API responses; update the Response returned in that exception
branch (the dict constructed in the except for UserNames.DoesNotExist) to
include "atcoder": {} so it matches the normal response shape returned later
(see the usual response dict with "codechef", "codeforces", "github",
"leetcode", "openlake", and "atcoder").

In `@api/leaderboard/views.py`:
- Around line 833-837: UserNamesList currently returns all UserNames to anyone;
update it to enforce authentication and/or scope results: add permission_classes
= [IsAuthenticated] on the UserNamesList class and change get to only return the
authenticated user’s records (e.g. UserNames.objects.filter(user=request.user))
or, if the model supports public visibility, filter to only public entries (e.g.
UserNames.objects.filter(is_public=True)) before serializing with
UserNamesSerializer; ensure you import and use
rest_framework.permissions.IsAuthenticated.

In `@app/src/components/Achievements.jsx`:
- Around line 73-74: The Authorization header currently uses authTokens.access
directly which can throw if authTokens is undefined; update the header
construction in Achievements.jsx to use optional chaining and a safe fallback
(e.g., `Bearer ${authTokens?.access || ''}`) or omit the header when no token is
present (build headers conditionally where the template literal
`'Authorization': `Bearer ${authTokens.access}`` is used) so that
authTokens.access is never accessed without a guard.
- Line 48: In fetchUnlocked, the Authorization header uses authTokens.access
directly which can throw if authTokens is undefined; change it to use optional
chaining (authTokens?.access) so the header construction is safe and consistent
with other calls—update the headers object in the fetchUnlocked function to
reference authTokens?.access and ensure any related bearer string formatting
still works when access is undefined.
- Around line 22-24: The fetch call in Achievements.jsx uses authTokens.access
directly which can throw if authTokens is null/undefined even when user is
truthy; update the logic around the fetch (the block that calls
fetch(`${BACKEND}/userDetails/`, { headers: { Authorization: `Bearer
${authTokens.access}` } })) to guard for missing tokens (e.g., check authTokens
&& authTokens.access or use optional chaining authTokens?.access) and bail out
or handle the no-token case (skip the request, set fallback state, or trigger
auth flow) so the component no longer throws a TypeError during auth
initialization race conditions.

---

Outside diff comments:
In `@api/leaderboard/api/views.py`:
- Around line 196-204: registerUser currently collects and saves cc_uname,
cf_uname, gh_uname, lt_uname into the UserNames model but omits the AtCoder
username; update the request parsing in registerUser to also extract ac_uname,
include ac_uname when constructing/saving the UserNames instance (UserNames(...,
ac_uname=ac_uname)), and, where other per-platform user records are created
(e.g., CodeChefUser/CodeForcesUser/LeetCodeUser creation logic), add creation of
the AtcoderUser record linked to the user using ac_uname so AtCoder usernames
are persisted consistently with post_UserNames.

In `@api/leaderboard/views.py`:
- Around line 753-774: The put handler DiscussionReplyManage.put currently
assumes a single reply per user per post by calling
ReplyPost.objects.get(username=request.user, parent=post), which can raise
MultipleObjectsReturned; require a reply identifier instead and fetch by it:
read reply_id from request.data (e.g., "reply_id"), then call
ReplyPost.objects.get(id=reply_id, username=request.user, parent=post) and
handle ReplyPost.DoesNotExist returning 404; alternatively, if you prefer not to
require an ID, replace the get with
ReplyPost.objects.filter(username=request.user, parent=post).first() and return
404 when the result is None—ensure you update validation and error messages
accordingly.

In `@app/src/components/Achievements.jsx`:
- Around line 16-61: The useEffect closure reads authTokens.access in
fetchAllStats and fetchUnlocked but the dependency array only lists [user,
BACKEND], causing stale tokens and an ESLint exhaustive-deps warning; update the
effect dependencies to include authTokens (or authTokens.access) and any setters
used, and ensure fetchAllStats and fetchUnlocked use the latest authTokens by
referencing the same variable inside the effect (functions: fetchAllStats,
fetchUnlocked) or memoizing tokens before the calls; also guard early when
authTokens or authTokens.access is falsy to avoid firing requests with undefined
tokens and maintain setIsLoading/setUnlockedAchievements behavior.
- Around line 64-102: The effect re-runs on every unlockedAchievements change
causing unnecessary full re-evaluations and possible race conditions; remove
unlockedAchievements from the useEffect dependency array so the effect only runs
on stats, user, BACKEND, or globalStreak changes, and prevent concurrent
duplicate unlock requests by adding an in-flight tracker (e.g., a useRef Set
called inflightUnlocks) that unlockAchievement checks/updates before sending a
POST; keep setUnlockedAchievements using the functional updater
(setUnlockedAchievements(prev => [...prev, newUnlock])) as used to avoid lost
updates from concurrent responses.

In `@app/src/components/Blogs.jsx`:
- Around line 39-41: The useEffect currently calls fetchBlogs() with an empty
dependency array, but fetchBlogs reads authTokens?.access so add authTokens to
the dependency array of the useEffect that invokes fetchBlogs (i.e., update the
effect that contains useEffect(() => { fetchBlogs(); }, [])) so it re-runs when
authTokens changes; ensure fetchBlogs is stable (memoize with useCallback if
necessary) or include its reference in the dependency list to avoid stale
closures.

---

Nitpick comments:
In `@api/leaderboard/api/views.py`:
- Around line 152-156: This module contains a duplicate import and redundant
logging setup: remove the second "import logging", the
"logging.basicConfig(...)" call and the duplicate "logger =
logging.getLogger(__name__)" so the module uses the single existing logger
instance already initialized elsewhere (the earlier logger variable). Ensure no
calls to logging.basicConfig remain in this module and that all logging uses the
existing logger symbol.

In `@api/leaderboard/users.py`:
- Around line 72-77: Replace the bare except and inline import: move the
AtcoderUserSerializer import to the module-level with other serializers, then
wrap the lookup in a try/except that only handles AtcoderUser.DoesNotExist (i.e.
catch that specific exception from AtcoderUser.objects.get) and set
atcoderDetails = {} in that case; do not silence other exceptions so errors
propagate. Ensure you reference AtcoderUser.objects.get and
AtcoderUserSerializer when updating the code.

In `@api/leaderboard/views.py`:
- Around line 349-360: The code is comparing naive datetimes; replace uses of
datetime.now() with Django's timezone-aware now(): import
django.utils.timezone.now and update the cc_users.filter(...) condition and the
assignment user.last_updated = ... to use timezone.now() instead of
datetime.now(); keep timedelta usage as-is and leave get_codechef_data,
user.rating, user.save() logic unchanged.
- Line 696: Remove the duplicate import of IsAuthenticatedOrReadOnly from
rest_framework.permissions in the file; locate the redundant import statement
(the one shown at the top of the snippet) and delete it so only the original
import (already present near the top of the module) remains, ensuring no other
imports on that line are affected.
- Around line 593-608: The get method on the view is causing an N+1 query by
calling PostVote.objects.get for every DiscussionPost; fix it by loading all
relevant votes in one query when request.user.is_authenticated (use
PostVote.objects.filter(user=request.user, post__in=posts)), build a mapping
from post id to vote_type, then iterate posts and set post_data["user_vote"] =
votes_map.get(post.id, None) before appending; keep using
DiscussionPost_Serializer(post).data and only fall back to per-post logic when
the user is anonymous.

In `@app/src/components/Blogs.jsx`:
- Around line 75-96: The optimistic update in handleInteract incorrectly handles
swap cases and can produce negative counts; refactor the setBlogs update for
clarity: compute alreadyVoted = b.user_vote === action and swapping =
b.user_vote && b.user_vote !== action, then update likes and dislikes with
explicit branches that (1) if alreadyVoted decrement the respective counter
using Math.max(0, ...) to avoid negatives and set user_vote null, (2) if
swapping decrement the old counter using Math.max(0, ...) and increment the new
counter, and (3) if neither just increment the targeted counter; update
user_vote to alreadyVoted ? null : action so counts always reflect the swap
correctly in handleInteract and the mapped blog object.
- Around line 112-114: The catch block currently swallows errors and only calls
fetchBlogs(), which hides network failures and provides no user feedback after
an optimistic update; change the catch to accept the error (catch (err)) and in
that handler: log the error, show a user-facing notification (e.g., call your
toast or setError handler) explaining the vote failed, and revert the optimistic
UI by calling fetchBlogs() (or undo the local state change) so the UI and server
stay consistent; update the catch around the network-vote call (the function
invoking fetchBlogs() and the optimistic update, e.g., voteBlog/handleVote) to
implement these steps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67ae560e-5ad9-41e9-9142-fc76a91af64d

📥 Commits

Reviewing files that changed from the base of the PR and between ebaed51 and 0964cde.

📒 Files selected for processing (11)
  • api/leaderboard/api/views.py
  • api/leaderboard/migrations/0014_postvote.py
  • api/leaderboard/migrations/0015_usernames_ac_uname.py
  • api/leaderboard/models.py
  • api/leaderboard/users.py
  • api/leaderboard/views.py
  • app/src/Context/AuthContext.jsx
  • app/src/components/Achievements.jsx
  • app/src/components/Blogs.jsx
  • app/src/components/HomePage.jsx
  • app/src/components/Profile.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/components/HomePage.jsx

Comment thread api/leaderboard/users.py
Comment thread api/leaderboard/views.py
Comment on lines +22 to +24
const res = await fetch(`${BACKEND}/userDetails/`, {
headers: { Authorization: `Bearer ${authTokens.access}` },
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential crash if authTokens is null/undefined when user exists.

Line 23 accesses authTokens.access directly without optional chaining. If there's a brief window where user is truthy but authTokens hasn't been set yet (race condition during auth initialization), this will throw a TypeError.

Proposed fix: Add optional chaining and guard
     const fetchAllStats = async () => {
       try {
         setIsLoading(true);
+        if (!authTokens?.access) {
+          setIsLoading(false);
+          return;
+        }
         const res = await fetch(`${BACKEND}/userDetails/`, {
-          headers: { Authorization: `Bearer ${authTokens.access}` },
+          headers: { Authorization: `Bearer ${authTokens?.access}` },
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Achievements.jsx` around lines 22 - 24, The fetch call in
Achievements.jsx uses authTokens.access directly which can throw if authTokens
is null/undefined even when user is truthy; update the logic around the fetch
(the block that calls fetch(`${BACKEND}/userDetails/`, { headers: {
Authorization: `Bearer ${authTokens.access}` } })) to guard for missing tokens
(e.g., check authTokens && authTokens.access or use optional chaining
authTokens?.access) and bail out or handle the no-token case (skip the request,
set fallback state, or trigger auth flow) so the component no longer throws a
TypeError during auth initialization race conditions.

Comment thread app/src/components/Achievements.jsx Outdated
Comment on lines +73 to 74
'Authorization': `Bearer ${authTokens.access}`
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same optional chaining issue with authTokens.access.

Consistent with the earlier finding, this line also accesses authTokens.access without optional chaining.

Proposed fix
                      headers: {
                          'Content-Type': 'application/json',
-                         'Authorization': `Bearer ${authTokens.access}`
+                         'Authorization': `Bearer ${authTokens?.access}`
                      },
📝 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
'Authorization': `Bearer ${authTokens.access}`
},
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authTokens?.access}`
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Achievements.jsx` around lines 73 - 74, The Authorization
header currently uses authTokens.access directly which can throw if authTokens
is undefined; update the header construction in Achievements.jsx to use optional
chaining and a safe fallback (e.g., `Bearer ${authTokens?.access || ''}`) or
omit the header when no token is present (build headers conditionally where the
template literal `'Authorization': `Bearer ${authTokens.access}`` is used) so
that authTokens.access is never accessed without a guard.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

Unrecognized words (34)
amaydixit
arpit
atcoder
atcoderuser
Bhilai
createsuperuser
ctz
eab
efcajlnqvdqjeoud
FCalcutta
fns
FOSSOVERFLOW
grindset
gtcvau
Hacktoberfest
heatmap
ical
idx
iframe
IIT
kenkoooo
lccal
leetcodeuser
linecap
linejoin
lstrip
Maxed
noopener
postvote
sparkline
spsiphnqk
startswith
ulk
upsert
These words are not needed and should be removed CRA elems leetcoderankingccps

Some files were automatically ignored 🙈

These sample patterns would exclude them:

^\Q.cspell.json\E$

You should consider adding them to:

.github/actions/spelling/excludes.txt

File matching is via Perl regular expressions.

To check these files, more of their words need to be in the dictionary than not. You can use patterns.txt to exclude portions, add items to the dictionary (e.g. by adding them to allow.txt), or fix typos.

To accept these unrecognized words as correct, update file exclusions, and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the git@github.com:Arpit-Mahajan09/Leaderboard-Pro.git repository
on the feature/my-changes branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/OpenLake/Leaderboard-Pro/actions/runs/23354018728/attempts/1' &&
git commit -m 'Update check-spelling metadata'
Available 📚 dictionaries could cover words (expected and unrecognized) not in the 📘 dictionary

This includes both expected items (592) from .github/actions/spelling/expect.txt and unrecognized words (34)

Dictionary Entries Covers Uniquely
cspell:django/dict/django.txt 393 64 19
cspell:software-terms/dict/softwareTerms.txt 1288 106 15
cspell:python/src/common/extra.txt 741 21 13
cspell:npm/dict/npm.txt 302 46 10
cspell:fullstack/dict/fullstack.txt 419 43 8

Consider adding them (in .github/workflows/spelling.yml) in jobs:/spelling::

      with:
        extra_dictionaries: |
          cspell:django/dict/django.txt
          cspell:software-terms/dict/softwareTerms.txt
          cspell:python/src/common/extra.txt
          cspell:npm/dict/npm.txt
          cspell:fullstack/dict/fullstack.txt

To stop checking additional dictionaries, add (in .github/workflows/spelling.yml):

check_extra_dictionaries: ""
Warnings ⚠️ (1)

See the 📂 files view, the 📜action log, or 📝 job summary for details.

⚠️ Warnings Count
⚠️ noisy-file 1

See ⚠️ Event descriptions for more information.

If you see a bunch of garbage

If it relates to a ...

well-formed pattern

See if there's a pattern that would match it.

If not, try writing one and adding it to the patterns.txt file.

Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

Note that patterns can't match multiline strings.

binary-ish string

Please add a file path to the excludes.txt file instead of just accepting the garbage.

File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

@sumagnadas sumagnadas merged commit cce848f into OpenLake:main Mar 24, 2026
1 of 2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants