Skip to content

feat: upcoming contests tracker for Codeforces with in-app reminders - #269

Merged
kunalverma2512 merged 4 commits into
kunalverma2512:mainfrom
ida-jemi:feat/upcoming-contests-tracker-253
Jul 10, 2026
Merged

feat: upcoming contests tracker for Codeforces with in-app reminders #269
kunalverma2512 merged 4 commits into
kunalverma2512:mainfrom
ida-jemi:feat/upcoming-contests-tracker-253

Conversation

@ida-jemi

@ida-jemi ida-jemi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

📌 Pull Request Summary

🔗 Related Issue

Closes #253


📝 Description

Adds a real, live Upcoming Contests Tracker for Codeforces with in-app reminders, replacing the static placeholder content on /contests/codeforces with real data and wiring reminders through the dashboard and navbar.

Changes Made

Backend

  • New Contest model - caches Codeforces' contest.list response (id, name, phase, parsed division, start time, duration).
  • New ContestReminder model - one doc per (user, contest) opt-in, with a notifiedAt flag so in-app toasts never repeat.
  • New contests module (routes / controller / service / repository / validation) following the existing feature-module pattern used by codeforces:
    • GET /api/contests/codeforces/upcoming - public, returns cached upcoming/running contests.
    • GET /api/contests/reminders - a user's reminder contest ids.
    • GET /api/contests/reminders/active - full contest details for reminders (powers the badge/toast).
    • POST /api/contests/reminders / DELETE /api/contests/reminders/:contestId - set/unset a reminder.
    • POST /api/contests/reminders/:contestId/notified - mark a reminder's toast as shown.
  • New server/jobs/contestSync.js - node-cron job that syncs the Codeforces contest list on startup and hourly, so the frontend never calls the CF API directly (avoids rate limits, keeps the page fast).
  • Added node-cron dependency.

Frontend

  • useContests hook - fetches cached contests, ticks a shared 1s clock for live countdowns, manages optimistic reminder toggling.
  • UpcomingContestsList - division-filterable contest cards with live countdown + "Remind Me" toggle (used both full-size on the contest page and in compact mode elsewhere).
  • ContestCountdown - pure countdown formatter (dd:hh:mm:ss / "Live Now").
  • UpcomingContestsWidget - compact top-3 dashboard widget, added to DashboardPage.
  • ContestReminderBell - navbar badge showing reminders due within 24h, linking to the tracker.
  • ContestReminderNotifier - mounted once in MainLayout; polls reminders every 30s and pops an in-app toast when a reminder-contest starts within 15 minutes (persisted server-side so it survives reloads/navigation).
  • ContestCodeforcesPage now leads with the live tracker, with the existing editorial archive section kept below it.

Motivation

Issue #253 asked for a way to see upcoming Codeforces contests and get reminded about them without leaving CodeLens. This wires up real Codeforces data (cached and refreshed hourly server-side) end-to-end: dashboard widget → dedicated tracker page → reminder opt-in → navbar badge → in-app toast when a contest is about to start.


🚀 Type of Change

  • New Feature

🧪 Testing

Verification

  • Tested Locally
  • No Testing Required (no existing test suite in the repo for this module)

Test Details

  • Ran node --check on every new/modified backend file, and smoke-imported app.js / the new contests routes module to confirm they load without errors.
  • Ran npm run lint (ESLint) on all new/modified frontend files - zero new warnings/errors introduced (pre-existing, unrelated Navbar.jsx issues on main are untouched by this PR).
  • Ran npm run build (Vite) - production build succeeds.
  • Manually verified: contest list renders with live countdowns, division filter works, reminder toggle updates optimistically and persists, dashboard widget shows top 3 contests, navbar bell badge reflects reminders due soon, and the in-app toast fires within the 15-minute window and doesn't repeat.

📸 Screenshots / Demo (If Applicable)

Screen.Recording.2026-07-07.194633.mp4

✅ Checklist

  • I have read and followed the contribution guidelines.
  • I have self-reviewed my changes.
  • My changes are limited to the scope of this issue.
  • Documentation has been updated where necessary.
  • No unnecessary files or unrelated changes have been included.
  • The related issue has been linked correctly.
  • All applicable testing and validation steps have been completed.

📚 Additional Notes

  • Reminders and the notifier only require Codeforces (no OAuth to CF needed) — this uses the same public contest.list endpoint already wrapped in server/utils/codeforcesApi.js.
  • The Contest model is intentionally keyed by platform so CodeChef/LeetCode/AtCoder trackers can reuse the same collection/module shape in a follow-up issue instead of duplicating it.
  • server/package-lock.json is updated because node-cron was added as a dependency.

Summary by CodeRabbit

  • New Features
    • Added “Upcoming Contests” section on the Codeforces page and an “Upcoming Contests” widget on the dashboard.
    • Enhanced contest countdowns with Live Now, Ended, and day-aware hh:mm:ss formatting, plus a compact display mode.
    • Introduced contest reminders with a bell badge, reminder toggles on contest cards, and bottom-right “starting soon” notifications.
  • UI Improvements
    • Refreshed the Codeforces page visuals with updated lucide icons and updated hero styling.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

@ida-jemi is attempting to deploy a commit to the Kunal Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🎉 Welcome to CodeLens — Thank You for Your Contribution!

Hey @ida-jemi! 👋

We are genuinely excited to have you here. Every single PR — big or small — makes CodeLens better, and yours is no exception. Take a moment to review the checklist below to help us merge your work quickly and smoothly.

✅ Before Requesting a Review

  • Keep code clean, readable, and consistent with the existing codebase
  • Avoid unrelated or unnecessary file changes
  • Make sure the UI is fully responsive across all device sizes
  • Attach screenshots or a short screen recording for any UI changes
  • Resolve all merge conflicts before marking the PR as ready
  • Do not submit AI-generated, copy-pasted, or low-effort implementations

💬 Join Our Community Channel — This is Mandatory

Being part of our communication channel is compulsory for all contributors, not optional.

📡 Join the CodeLens Matrix Channel

Why join? This is where all important announcements, PR review updates, contribution discussions, and maintainer decisions happen in real time. Contributors who are not in the channel regularly miss critical context and updates, which often leads to duplicated or misaligned work. Staying connected here is what keeps the community strong and your contributions impactful.


We are rooting for you! If you have any questions, drop them in the channel or comment right here on this PR. Let's build something great together. 🚀✨

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9487facd-d592-423a-a29f-f496c92b1d7a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds Codeforces upcoming contests support with cached contest data, reminder persistence and APIs, an hourly sync job, and frontend contest display and notification UI wired into the dashboard, contest page, navbar, and layout.

Changes

Contest Tracker Feature

Layer / File(s) Summary
Data models
server/models/Contest.js, server/models/ContestReminder.js
New Mongoose schemas cache Codeforces contests and store per-user reminders with compound unique indexes.
Repository layer
server/modules/contests/repository.js
Adds bulk upsert, upcoming contest queries, reminder CRUD/query helpers, active reminder joins, notification marking, and stale reminder pruning.
Service, controller, validation, routes
server/modules/contests/service.js, server/modules/contests/controller.js, server/modules/contests/validation.js, server/modules/contests/routes.js
Normalizes and syncs contest data, exposes reminder and contest handlers, validates reminder input, and wires public/authenticated routes.
Server wiring and sync job
server/app.js, server/jobs/contestSync.js, server/server.js, server/package.json
Mounts /api/contests, starts hourly contest sync on server boot, and adds node-cron.
Frontend service and hook
frontend/src/services/contestService.js, frontend/src/hooks/useContests.js
Adds contest/reminder API wrappers and a hook for loading contests, reminders, live countdown data, and optimistic reminder toggling.
Contest display and page wiring
frontend/src/components/contests/ContestCountdown.jsx, frontend/src/components/contests/UpcomingContestsList.jsx, frontend/src/components/dashboard/UpcomingContestsWidget.jsx, frontend/src/pages/DashboardPage.jsx, frontend/src/pages/ContestCodeforcesPage.jsx
Adds countdown and contest list UI, a dashboard widget, and page integrations for upcoming contests.
Reminder bell and toast notifier
frontend/src/components/contests/ContestReminderBell.jsx, frontend/src/components/shared/ContestReminderNotifier.jsx, frontend/src/components/shared/Navbar.jsx, frontend/src/layouts/MainLayout.jsx
Adds a reminder badge in the navbar and a global toast notifier for imminent contests.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a Codeforces upcoming-contests tracker with reminders.
Description check ✅ Passed The PR follows the template with related issue, summary, changes, motivation, testing, screenshots, checklist, and notes.
Linked Issues check ✅ Passed The changes implement #253’s tracker, live countdowns, reminders, cached server-side syncing, and in-app/badge notifications.
Out of Scope Changes check ✅ Passed No clearly unrelated changes stand out; the UI polish and layout updates still support the Codeforces tracker feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (4)
server/models/ContestReminder.js (1)

9-14: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant single-field index on user.

The compound unique index at Line 27-30 already starts with user, so it can satisfy any query filtering by user alone. The index: true on the user field (Line 13) creates a second, unnecessary index that adds write/storage overhead without new query coverage.

♻️ Proposed fix
     user: {
       type: mongoose.Schema.Types.ObjectId,
       ref: "User",
       required: true,
-      index: true,
     },

Also applies to: 27-30

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

In `@server/models/ContestReminder.js` around lines 9 - 14, The `ContestReminder`
schema has a redundant standalone index on `user`; remove the `index: true` from
the `user` field and keep the compound unique index in the schema so queries by
`user` are still covered without duplicating index maintenance. Update the
`mongoose.Schema` definition in `ContestReminder.js` by adjusting the `user`
field and leaving the compound index declaration as the single source of
indexing for this key.
server/modules/contests/repository.js (1)

79-93: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

pruneStaleReminders scans all historical finished contests instead of scoping to active reminders.

This query fetches every FINISHED contest document in the collection on every hourly sync, which grows unbounded with the total number of Codeforces contests ever cached, not with the (much smaller) number of active reminders. Scoping the lookup to contest IDs that actually have reminders first is more efficient and scales with reminder count instead of total contest history.

♻️ Proposed fix
   static async pruneStaleReminders(platform = "codeforces") {
-    const finishedContestIds = await Contest.find({
-      platform,
-      phase: "FINISHED",
-    })
-      .select("contestId -_id")
-      .lean();
-
-    if (!finishedContestIds.length) return;
-
-    await ContestReminder.deleteMany({
-      platform,
-      contestId: { $in: finishedContestIds.map((c) => c.contestId) },
-    });
+    const reminderContestIds = await ContestReminder.find({ platform })
+      .distinct("contestId");
+
+    if (!reminderContestIds.length) return;
+
+    const finishedContestIds = await Contest.find({
+      platform,
+      contestId: { $in: reminderContestIds },
+      phase: "FINISHED",
+    })
+      .select("contestId -_id")
+      .lean();
+
+    if (!finishedContestIds.length) return;
+
+    await ContestReminder.deleteMany({
+      platform,
+      contestId: { $in: finishedContestIds.map((c) => c.contestId) },
+    });
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/contests/repository.js` around lines 79 - 93,
`pruneStaleReminders` is loading every finished contest, which makes the hourly
cleanup scale with contest history instead of reminder usage. Update the
`Contest` lookup in `ContestReminder` pruning to first collect the contest IDs
that currently have reminders for the given platform, then query only matching
`FINISHED` contests and delete reminders for that intersection. Keep the
behavior inside `pruneStaleReminders` and use the existing `Contest` and
`ContestReminder` models to preserve the same deletion result with a smaller
scoped lookup.
frontend/src/components/contests/ContestReminderBell.jsx (1)

13-38: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Duplicate polling of getMyActiveReminders alongside ContestReminderNotifier.

This component polls /contests/reminders/active every 60s while frontend/src/components/shared/ContestReminderNotifier.jsx (mounted globally in MainLayout) independently polls the same endpoint every 30s. Since both are rendered simultaneously for every authenticated page, this results in two redundant, uncoordinated network requests to the same backend endpoint. Consider extracting a shared hook (e.g. useActiveReminders) or a small context provider that both components consume, so there's a single polling source of truth.

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

In `@frontend/src/components/contests/ContestReminderBell.jsx` around lines 13 -
38, The `ContestReminderBell` effect is duplicating the same active-reminders
polling already done by `ContestReminderNotifier`, causing redundant requests to
`/contests/reminders/active`. Refactor the polling logic in
`ContestReminderBell` and `ContestReminderNotifier` to consume a shared source
of truth, such as a `useActiveReminders` hook or a small context/provider, so
only one polling loop calls `getMyActiveReminders` and both components read from
the same reminder state.
frontend/src/components/contests/UpcomingContestsList.jsx (1)

38-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reminder intent lost on redirect to /login.

Unauthenticated users clicking "Remind Me" are redirected to /login with no way to resume the reminder toggle after authenticating.

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

In `@frontend/src/components/contests/UpcomingContestsList.jsx` around lines 38 -
49, The reminder action in handleReminderClick is lost when unauthenticated
users are redirected to login. Update the UpcomingContestsList flow so the
clicked contestId and reminder intent are preserved before navigating to /login,
then have the post-login path or login page recover that state and retry
toggleReminder(contestId) after authentication. Use the existing
handleReminderClick, navigate, and toggleReminder symbols to keep the reminder
request resumable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/contests/ContestReminderBell.jsx`:
- Around line 22-26: The due-soon calculation in ContestReminderBell.jsx only
applies an upper-bound window, so started or past contests can still be counted;
update the filter in the reminder badge logic to require the contest start time
to be in the future as well as within DUE_SOON_WINDOW_MS. Use the same
lower-bound check pattern already used in ContestReminderNotifier.jsx
(msUntilStart > 0) and apply it around the existing Date.now() /
startTimeSeconds comparison before setDueSoonCount runs.

In `@frontend/src/components/contests/UpcomingContestsList.jsx`:
- Around line 19-23: The duration formatter can render invalid values like “1h
60m” because formatDuration rounds the minute remainder. Update formatDuration
to normalize the remainder so minutes never reach 60, either by flooring the
minutes or by carrying any rounded overflow into hours, and keep the display
logic consistent for UpcomingContestsList.

In `@server/modules/contests/validation.js`:
- Around line 4-18: Update the validate middleware in validate(schema) so it
uses result.error.issues instead of result.error.errors when building the 400
response payload. The current mapping over errors breaks on Zod 4 invalid input,
so change the error collection inside the safeParse failure branch to read from
the ZodError issues array and keep the existing field/message shape in the JSON
response.

---

Nitpick comments:
In `@frontend/src/components/contests/ContestReminderBell.jsx`:
- Around line 13-38: The `ContestReminderBell` effect is duplicating the same
active-reminders polling already done by `ContestReminderNotifier`, causing
redundant requests to `/contests/reminders/active`. Refactor the polling logic
in `ContestReminderBell` and `ContestReminderNotifier` to consume a shared
source of truth, such as a `useActiveReminders` hook or a small
context/provider, so only one polling loop calls `getMyActiveReminders` and both
components read from the same reminder state.

In `@frontend/src/components/contests/UpcomingContestsList.jsx`:
- Around line 38-49: The reminder action in handleReminderClick is lost when
unauthenticated users are redirected to login. Update the UpcomingContestsList
flow so the clicked contestId and reminder intent are preserved before
navigating to /login, then have the post-login path or login page recover that
state and retry toggleReminder(contestId) after authentication. Use the existing
handleReminderClick, navigate, and toggleReminder symbols to keep the reminder
request resumable.

In `@server/models/ContestReminder.js`:
- Around line 9-14: The `ContestReminder` schema has a redundant standalone
index on `user`; remove the `index: true` from the `user` field and keep the
compound unique index in the schema so queries by `user` are still covered
without duplicating index maintenance. Update the `mongoose.Schema` definition
in `ContestReminder.js` by adjusting the `user` field and leaving the compound
index declaration as the single source of indexing for this key.

In `@server/modules/contests/repository.js`:
- Around line 79-93: `pruneStaleReminders` is loading every finished contest,
which makes the hourly cleanup scale with contest history instead of reminder
usage. Update the `Contest` lookup in `ContestReminder` pruning to first collect
the contest IDs that currently have reminders for the given platform, then query
only matching `FINISHED` contests and delete reminders for that intersection.
Keep the behavior inside `pruneStaleReminders` and use the existing `Contest`
and `ContestReminder` models to preserve the same deletion result with a smaller
scoped lookup.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: faa3cc58-c5a6-4adc-b307-37bc44f5d65e

📥 Commits

Reviewing files that changed from the base of the PR and between f3a097f and 214556d.

⛔ Files ignored due to path filters (1)
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • frontend/src/components/contests/ContestCountdown.jsx
  • frontend/src/components/contests/ContestReminderBell.jsx
  • frontend/src/components/contests/UpcomingContestsList.jsx
  • frontend/src/components/dashboard/UpcomingContestsWidget.jsx
  • frontend/src/components/shared/ContestReminderNotifier.jsx
  • frontend/src/components/shared/Navbar.jsx
  • frontend/src/hooks/useContests.js
  • frontend/src/layouts/MainLayout.jsx
  • frontend/src/pages/ContestCodeforcesPage.jsx
  • frontend/src/pages/DashboardPage.jsx
  • frontend/src/services/contestService.js
  • server/app.js
  • server/jobs/contestSync.js
  • server/models/Contest.js
  • server/models/ContestReminder.js
  • server/modules/contests/controller.js
  • server/modules/contests/repository.js
  • server/modules/contests/routes.js
  • server/modules/contests/service.js
  • server/modules/contests/validation.js
  • server/package.json
  • server/server.js

Comment thread frontend/src/components/contests/ContestReminderBell.jsx
Comment thread frontend/src/components/contests/UpcomingContestsList.jsx
Comment thread server/modules/contests/validation.js
- fix due-soon badge counting started/past contests
- fix duration formatter rounding overflow (60m -> next hour)
- use ZodError.issues instead of removed .errors (Zod v4)
@kunalverma2512

Copy link
Copy Markdown
Owner

Hi @ida-jemi,

I finally had the opportunity to go through your implementation, and I must say I'm genuinely impressed. This is a remarkably comprehensive full-stack contribution. It's evident that you approached the feature with a strong engineering mindset rather than simply making it work.

What stood out the most was how meticulously you've organized the implementation. The project structure, module separation, folder hierarchy, reusable components, hooks, services, and overall code organization demonstrate that you invested time in understanding the existing architecture before building on top of it. That level of discipline is something I deeply appreciate because it shows a solid understanding of the project instead of treating it as an isolated feature.

Contributors who can architect and deliver end to end features like this are quite rare. This kind of ownership and execution is exactly what makes an exceptional open source contributor. I genuinely hope you continue taking on ambitious features like this because I believe you can make a significant impact on CodeLens.

I'd really appreciate it if you could record another demo video showcasing the entire feature from an end user's perspective. I'd love to see the complete workflow from viewing upcoming contests, setting reminders, the dashboard widget, navbar badge, reminder notifications, and how everything behaves in real world usage. It would also be great if you could use your own voice while explaining the implementation, the architecture, and the reasoning behind your design decisions. Think of it as a short technical presentation where you walk me through both the user experience and the codebase.

I'd also love to have a short Google Meet with you to discuss this feature in more detail. It's an exceptional contribution, and I think a quick technical discussion would be valuable before we move forward. Let me know a time that works for you.

Also, if you haven't already, please join our communication channel on Matrix. It'll make it much easier for us to discuss this feature and a few additional ideas going forward.

@ida-jemi

ida-jemi commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Hi @kunalverma2512 Thank you for the review and feedback!

Here's a video of a complete walkthrough:

CodeLens.1.mp4

Note: The bell is a simple link to the tracker rather than a dropdown, since the toast and dashboard widget already surface reminder details, this keeps the navbar footprint minimal for v1.


Explanation of the architechture and reasoning:

  • The data model (Contest.js and ContestReminder.js) :
    I kept these as two separate collections rather than embedding reminders in the User doc, because reminders are a many-to-many relationship between users and contests, and this keeps queries simple with a compound unique index.

  • jobs/contestSync.js :
    Codeforces' API is public but rate-limited, so instead of calling it on every page load, I sync it into MongoDB once on server startup and then hourly via node-cron. The frontend only ever talks to our own cached endpoint.

  • The module layering (modules/contests/folder) :
    routes → controller → service → repository

    • This mirrors the existing codeforces module's structure in the codebase, so it stays consistent with how the rest of the app is organized.
    • public/protected split in routes.js: The upcoming-contests endpoint is public since Codeforces' own data is public too, but everything reminder-related requires auth, that's enforced by where router.use(authMiddleware) sits in the file.
  • Frontend state (useContests.js) :
    One shared setInterval ticks a single clock for all countdown cards, instead of each card running its own timer, that's the now state here, recomputed into msUntilStart via useMemo

  • ContestReminderNotifier.jsx and the notifiedAt field
    Each reminder only ever toasts once, the backend marks it as shown so it survives page reloads and doesn't spam the user

Also, I couldn't find a link to the Matrix channel anywhere in the README/CONTRIBUTING docs, it would be great if you could help me with that. Thank you!!

@kunalverma2512

kunalverma2512 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

@ida-jemi Good and the presentation was so much beautiful because its rarely contributors shows inspect , changes in DB to show future actions and other stuffs you showed for which I appreciate your whole work

Click here to visit our matrix channel (😄 btw when you make PR then in Welcome message Chatbot it shows out communication matrix channel)

Apart from this I see their some work more to do before merging it

Some work I can tell here but some need dedicated discussion for which try to schedule a google meet as possible (text me on communication channel)

  1. I think using emoji's make the UI look bad so we can avoid emoji's i.e remove all emoji's from everywhere you used and use lucide icons if theirs something that can be used
  2. Some work to do in layout when you are presenting those UPCOMING CONTEST in dashboard as current UI seems to be too much stretched and less atttractive
  3. in codeforces arsenal page the first section background is not white (I prefer to make bg of that first section pure white)
  4. more further changes I'll discuss in meet (lemme know in matrix channel to confirm the schedule)

complete till point 3 and then further discussion as said in point 4 above

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
frontend/src/components/contests/UpcomingContestsList.jsx (1)

20-24: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

formatDuration still produces invalid "60m" display — past fix not applied.

Math.round((durationSeconds % 3600) / 60) can yield 60 (e.g., durationSeconds = 7199"1h 60m"). This was flagged in a prior review and marked addressed, but the current code still has the same pattern.

🐛 Proposed fix
 function formatDuration(durationSeconds) {
-  const hours = Math.floor(durationSeconds / 3600);
-  const minutes = Math.round((durationSeconds % 3600) / 60);
-  return minutes ? `${hours}h ${minutes}m` : `${hours}h`;
+  const totalMinutes = Math.round(durationSeconds / 60);
+  const hours = Math.floor(totalMinutes / 60);
+  const minutes = totalMinutes % 60;
+  return minutes ? `${hours}h ${minutes}m` : `${hours}h`;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/contests/UpcomingContestsList.jsx` around lines 20 -
24, `formatDuration` in UpcomingContestsList still rounds minutes in a way that
can produce an invalid 60m display. Update the duration formatting logic inside
`formatDuration` so the minutes value is derived without rounding up into 60,
and handle any carry into hours before returning the formatted string; use the
existing `formatDuration` helper to locate the fix.
🧹 Nitpick comments (1)
frontend/src/components/contests/UpcomingContestsList.jsx (1)

166-187: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add aria-pressed to the reminder toggle button for screen reader accessibility.

The button's visual styling changes based on contest.hasReminder, but the state isn't conveyed to assistive technology. Adding aria-pressed={contest.hasReminder} lets screen readers announce the toggle state.

♿ Proposed fix
             <button
+              aria-pressed={contest.hasReminder}
               onClick={() => handleReminderClick(contest.contestId)}
               className={`mt-auto w-full flex items-center justify-center gap-2 border-4 border-black font-black uppercase tracking-widest transition-colors ${
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/contests/UpcomingContestsList.jsx` around lines 166 -
187, The reminder button in UpcomingContestsList should expose its toggle state
to assistive tech by adding an aria-pressed attribute driven by
contest.hasReminder. Update the button element in the reminder toggle block so
the accessibility state matches the existing visual state changes, using the
same contest.hasReminder value that already controls the label and styling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@frontend/src/components/contests/UpcomingContestsList.jsx`:
- Around line 20-24: `formatDuration` in UpcomingContestsList still rounds
minutes in a way that can produce an invalid 60m display. Update the duration
formatting logic inside `formatDuration` so the minutes value is derived without
rounding up into 60, and handle any carry into hours before returning the
formatted string; use the existing `formatDuration` helper to locate the fix.

---

Nitpick comments:
In `@frontend/src/components/contests/UpcomingContestsList.jsx`:
- Around line 166-187: The reminder button in UpcomingContestsList should expose
its toggle state to assistive tech by adding an aria-pressed attribute driven by
contest.hasReminder. Update the button element in the reminder toggle block so
the accessibility state matches the existing visual state changes, using the
same contest.hasReminder value that already controls the label and styling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ecc4d15-88c1-441c-867a-18027497d88a

📥 Commits

Reviewing files that changed from the base of the PR and between e66bdf5 and 4d6cb0f.

📒 Files selected for processing (6)
  • frontend/src/components/contests/ContestCountdown.jsx
  • frontend/src/components/contests/ContestReminderBell.jsx
  • frontend/src/components/contests/UpcomingContestsList.jsx
  • frontend/src/components/dashboard/UpcomingContestsWidget.jsx
  • frontend/src/components/shared/ContestReminderNotifier.jsx
  • frontend/src/pages/ContestCodeforcesPage.jsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/src/components/dashboard/UpcomingContestsWidget.jsx
  • frontend/src/pages/ContestCodeforcesPage.jsx
  • frontend/src/components/contests/ContestReminderBell.jsx
  • frontend/src/components/shared/ContestReminderNotifier.jsx

@ida-jemi

ida-jemi commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@kunalverma2512 I've addressed all three:

  1. Removed all emojis in the contest feature (and a few pre-existing ones in the same page I was already touching), replaced with lucide-react icons to match the rest of the app
image
  1. Dashboard widget now uses a responsive 3-column grid instead of stacked full-width cards, with tighter spacing
Screenshot 2026-07-09 000215
  1. Contest Arsenal hero section background is now pure white
image
  1. I've joined the matrix channel and will confirm the schedule

@kunalverma2512

Copy link
Copy Markdown
Owner

@ida-jemi Thanks and I'll going to review asap and get in conversation on matrix with you soon

@kunalverma2512

Copy link
Copy Markdown
Owner

@coderabbitai

Please perform a COMPLETE production-level review of this PR. This PR introduces an entirely new feature with backend, frontend, database models, APIs, scheduled jobs, hooks, services, layouts, pages, and shared components while also modifying existing parts of the codebase.

Do NOT perform a surface-level review. Review every changed file and every changed line of code.

I want this review to be as if you are the lead maintainer responsible for approving a production release.

Please verify all of the following:

1. Code Correctness

  • Review every function, component, hook, API, service, repository, model, controller, validation, and utility.
  • Identify logical bugs, hidden edge cases, race conditions, incorrect assumptions, dead code, unreachable code, and incorrect implementations.
  • Check async flows, promises, error handling, retries, cleanup logic, and side effects.

2. Existing Codebase Compatibility

  • Ensure every new change integrates correctly with the existing architecture.
  • Detect breaking changes.
  • Identify regressions this PR could introduce.
  • Verify imports, exports, routes, layouts, shared components, contexts, hooks, and services remain compatible.
  • Check whether existing features can silently break because of this implementation.

3. Architecture Review

Review whether the implementation follows good software engineering principles.

Check:

  • Separation of concerns
  • Folder organization
  • Module boundaries
  • Reusability
  • Scalability
  • Maintainability
  • SOLID principles where applicable
  • Consistency with the existing project architecture

Point out any architectural smells or future maintenance risks.

4. Frontend Review

Inspect every React component.

Check:

  • State management
  • Hooks usage
  • Dependency arrays
  • Re-renders
  • Memoization opportunities
  • Component composition
  • Accessibility
  • Responsiveness
  • UI consistency
  • Loading states
  • Empty states
  • Error states
  • Memory leaks
  • Cleanup of intervals/timeouts/events

5. Backend Review

Review every backend file.

Check:

  • API design
  • Validation
  • Controllers
  • Services
  • Repository layer
  • Database queries
  • Data consistency
  • Authorization
  • Authentication
  • Error handling
  • Cron jobs
  • Scheduling logic
  • Duplicate execution risks
  • Data integrity

6. Database Review

Review models and persistence.

Check:

  • Schema design
  • Indexes
  • Constraints
  • Unique keys
  • Query efficiency
  • Duplicate records
  • Missing validation
  • Data consistency
  • Future scalability

7. Performance Review

Identify:

  • Unnecessary renders
  • Expensive computations
  • Memory leaks
  • N+1 queries
  • Duplicate API calls
  • Inefficient loops
  • Polling issues
  • Heavy operations
  • Network inefficiencies
  • Unnecessary state updates

Suggest optimizations wherever possible.

8. Security Review

Check for:

  • Missing authorization
  • Missing authentication
  • Injection risks
  • XSS
  • Sensitive data exposure
  • Input validation issues
  • API abuse possibilities
  • Missing permission checks
  • Unsafe client trust

9. Production Readiness

Identify anything that could fail in production including:

  • Rare edge cases
  • Concurrency issues
  • Timezone/date bugs
  • Scheduling bugs
  • Network failures
  • API downtime handling
  • Retry strategies
  • Null/undefined handling
  • Large datasets
  • Unexpected user behavior

10. Open Source Quality

Review whether the implementation reflects professional open-source standards.

Comment on:

  • Naming
  • Readability
  • Maintainability
  • Code duplication
  • Simplicity
  • Documentation
  • Consistency
  • Overall engineering maturity

11. Testing Review

Point out scenarios that are currently untested.

List:

  • Unit tests that should exist
  • Integration tests
  • UI tests
  • API tests
  • Edge case tests
  • Regression tests

12. Review Output Format

For EVERY issue found, provide:

  • Severity (Critical / High / Medium / Low)
  • File name
  • Function or component
  • Exact reason why it is a problem
  • Real production impact
  • How to reproduce (if applicable)
  • Recommended fix
  • Code example for the fix where appropriate

Do not only report syntax or style issues. Focus primarily on correctness, architecture, reliability, maintainability, scalability, security, and production stability.

Finally, provide a summary with:

  • Critical issues count
  • High issues count
  • Medium issues count
  • Low issues count

Then answer these questions explicitly:

  1. Is this PR safe to merge?
  2. If not, what must be fixed before merging?
  3. What improvements are recommended but not required?
  4. What parts of the implementation are particularly well designed?
  5. Overall merge confidence score (0–10).
  6. Overall code quality score (0–10).

Please be extremely thorough. I would rather receive a long review than miss a production bug.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 269 --repo kunalverma2512/CodeLens --json headRefName,baseRefName,number -q '.'
gh pr checkout 269 --repo kunalverma2512/CodeLens || true
echo "---FILES---"
fd . server/models server/modules/contests server/jobs -e js
echo "---"
fd . frontend/src/components/contests frontend/src/components/dashboard frontend/src/components/shared frontend/src/hooks frontend/src/services frontend/src/pages frontend/src/layouts -e jsx -e js

Length of output: 3509


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for f in server/models/Contest.js server/models/ContestReminder.js \
  server/modules/contests/controller.js server/modules/contests/repository.js \
  server/modules/contests/routes.js server/modules/contests/service.js \
  server/modules/contests/validation.js server/jobs/contestSync.js \
  server/server.js server/app.js; do
  echo "===== $f ====="
  cat -n "$f"
  echo
done

Length of output: 19753


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for f in frontend/src/components/contests/ContestCountdown.jsx \
  frontend/src/components/contests/ContestReminderBell.jsx \
  frontend/src/components/contests/UpcomingContestsList.jsx \
  frontend/src/components/dashboard/UpcomingContestsWidget.jsx \
  frontend/src/components/shared/ContestReminderNotifier.jsx \
  frontend/src/hooks/useContests.js \
  frontend/src/services/contestService.js \
  frontend/src/services/api.js \
  frontend/src/pages/ContestCodeforcesPage.jsx \
  frontend/src/pages/DashboardPage.jsx; do
  echo "===== $f ====="
  cat -n "$f"
  echo
done

echo "===== server/utils/codeforcesApi.js ====="
cat -n server/utils/codeforcesApi.js 2>/dev/null || fd codeforcesApi.js -x cat -n {}

echo "===== Navbar diff context ====="
rg -n "ContestReminderBell" frontend/src/components/shared/Navbar.jsx -B3 -A3
echo "===== MainLayout diff context ====="
rg -n "ContestReminderNotifier" frontend/src/layouts/MainLayout.jsx -B5 -A5

Length of output: 48146


@kunalverma2512 Completed a full line-by-line review of every new/changed file (backend models, controller/service/repository/routes/validation, cron job, and all frontend hooks/components/pages). Here's the production-level breakdown.


Issues Found

🟠 High

H1 — Cron job has no multi-instance guard (duplicate execution risk)

  • File: server/jobs/contestSync.js, server/server.js
  • startContestSyncJob() runs node-cron in-process on every server startup. If this is ever deployed with more than one instance (Render autoscaling, PM2 cluster mode, multiple dynos), every instance runs its own hourly cron independently.
  • Impact: redundant calls to the public Codeforces API from N instances at the same minute, N-way duplicate bulkWrite/pruneStaleReminders execution. Not data-corrupting (upserts are idempotent), but wasteful and risks hitting Codeforces' informal rate limits faster as the app scales.
  • Fix: gate the cron with a lightweight DB-based lock (e.g., a SyncLock doc with a TTL, or findOneAndUpdate with a "leader" flag and short expiry) so only one instance runs the sync per interval, or move sync to a dedicated worker/cron service outside the web dyno.

🟡 Medium

M1 — Contests in system-testing phases go stale in DB

  • File: server/modules/contests/service.jssyncCodeforcesContests
  • relevant only keeps BEFORE, CODING, FINISHED. Codeforces contests also pass through PENDING_SYSTEM_TEST / SYSTEM_TEST before FINISHED. During that window the contest is silently excluded from docs, so its Mongo document is not updated — it keeps whatever phase (CODING, typically) it had last sync.
  • Impact: getUpcomingContests() filters phase in [BEFORE, CODING], so a contest that has actually stopped taking submissions can keep showing as "Live Now" (red pulsing badge) for up to an extra sync cycle (~1h) until the next run marks it FINISHED.
  • Fix: include PENDING_SYSTEM_TEST/SYSTEM_TEST in the relevant filter (map them to CODING-adjacent display state, or just also persist them so isRunning/countdown logic in the frontend can react correctly).

M2 — Reminder bell and reminder notifier poll the same endpoint independently

  • Files: frontend/src/components/contests/ContestReminderBell.jsx (60s) and frontend/src/components/shared/ContestReminderNotifier.jsx (30s)
  • Both are mounted simultaneously on every authenticated page (Navbar + MainLayout) and each runs its own setInterval calling getMyActiveReminders() — two independent, undeduplicated network calls to the same backend endpoint per polling window.
  • Impact: 2x unnecessary API/DB load per active authenticated tab, scales linearly with concurrent users. Not incorrect, just wasteful.
  • Fix: lift reminder polling into a single shared context/hook (or extend useContests) that both the bell and the notifier subscribe to, so there's one poll loop per tab instead of two.

M3 — "Top 20 most recent finished contests" window can permanently orphan a stale document

  • File: server/modules/contests/service.jssyncCodeforcesContests
  • Only the 20 most-recently-started FINISHED contests get re-upserted each run. Under normal cadence this always catches contests as they transition, but during a burst (marathon/multi-round events, or if the sync is down for a while) a contest could drop out of that window before ever being persisted with phase: "FINISHED", leaving it permanently stuck showing as upcoming/running in the DB.
  • Fix: either don't cap the finished slice (Codeforces' full list isn't huge), or explicitly upsert phase transitions for any contest currently in the DB with phase != FINISHED regardless of the 20-item cap.

M4 — Multi-tab duplicate toast risk

  • File: frontend/src/components/shared/ContestReminderNotifier.jsx
  • shownThisSession is a component-instance Set, not shared across browser tabs. Two tabs open simultaneously can both observe notifiedAt == null before either tab's fire-and-forget markReminderNotified() call resolves, and both would render the toast.
  • Impact: cosmetic double-toast, not data corruption — low real-world likelihood but worth flagging since it's part of the "production readiness" ask.
  • Fix (optional): not blocking; could dedupe via localStorage/BroadcastChannel if this becomes a real complaint.

M5 — Division filter list is incomplete

  • File: frontend/src/components/contests/UpcomingContestsList.jsx
  • DIVISIONS only covers Div. 1–4, Educational, Global. parseDivision() on the backend can also produce "Div. 1 + 2", "Kotlin Heroes", "ICPC", "Other" — those contests are only reachable via the "all" filter, never individually filterable.
  • Fix: either add these to DIVISIONS or fold them into a catch-all "Other" filter bucket.

🟢 Low

  • L1server/modules/contests/controller.js removeReminder/markNotified parse req.params.contestId manually with parseInt + NaN check instead of running through the same validate(schema) middleware used for addReminder. Functionally fine (negative/zero IDs just won't match anything downstream), but inconsistent validation strategy across the same module.
  • L2server/models/Contest.js declares both a single-field index on contestId and a compound unique index on {platform, contestId}. Since platform currently has only one enum value, these largely overlap — redundant index maintenance overhead. Consider dropping the standalone contestId index.
  • L3frontend/src/hooks/useContests.js: fetchContests/fetchReminders don't guard against setState after unmount (no cancellation flag), unlike the bell/notifier components which correctly use a cancelled flag. Not a leak in React 18, but inconsistent with the cleanup discipline shown elsewhere in this PR.
  • L4ContestReminderBell.jsx uses title for accessibility instead of aria-label; minor a11y nit for screen readers.
  • L5 — No automated tests were added for any new file (models, service, repository, controller, hooks, components).

What's particularly well designed

  • Optimistic UI + rollback in useContests.toggleReminder — clean, correct pattern.
  • Idempotent reminder writes: ContestRepository.addReminder uses $setOnInsert + the unique compound index, so a double-click/race can't create duplicate reminders — good defensive design.
  • Shared clock pattern: one setInterval in useContests recomputing msUntilStart/isRunning via useMemo for all cards, instead of N independent timers per card — solid perf choice, exactly right for this use case.
  • notifiedAt persistence for one-time toast delivery — survives reloads/navigation, correct design for the stated goal.
  • Module layering (routes → controller → service → repository) mirrors the existing codeforces module, and the public/protected route split (upcoming public, reminders behind authMiddleware) matches the stated security posture (public CF data, auth-gated personal state).
  • Contest existence + phase validation before allowing a reminder (ApiError 404/400 in service.addReminder) prevents reminders on contests that don't exist or have already ended — good input trust boundary.

Summary

  • Critical: 0
  • High: 1
  • Medium: 5
  • Low: 5
  1. Is this PR safe to merge? Yes — no critical or data-corrupting bugs found. All flagged issues are either cosmetic, self-healing within an hour, or only manifest under specific deployment/scaling conditions.
  2. What must be fixed before merging? Nothing is strictly blocking for a single-instance deployment. If you plan to scale to multiple server instances soon, H1 (cron duplication) should be addressed first.
  3. Recommended (not required): M1–M5 above, plus adding basic unit/integration tests for ContestService/ContestRepository and the reminder toggle flow.
  4. Well-designed parts: see above — optimistic UI rollback, idempotent upserts, shared countdown clock, and the layered module structure stand out.
  5. Merge confidence: 8/10
  6. Code quality: 8/10

(One nit for future PRs, not this one specifically): please continue documenting design rationale in comments as you did with ContestCountdown.jsx and ContestReminderNotifier.jsx — it made this review significantly faster and more accurate.)

٩(◕‿◕)۶

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kunalverma2512

Copy link
Copy Markdown
Owner

@ida-jemi I see this PR will not produce any bugs in production but theirs some high,medium,low issues that coderabbitai has mentioned

I want a confirmation that if I merge this then will you be working on the issues mentioned by coderabbitai ?
I prefer that since this feature is completely made by you and hence you have good understanding and issue of HIGH category is neccessary so if you confirm that you will can work HIGH mentioned issue by coderabbit in separate issue then lemme know so that I can merge this and then I will create a new issue (i.e of "Cron job has no multi-instance guard (duplicate execution risk)") related to current issue ?

@ida-jemi

Copy link
Copy Markdown
Contributor Author

Hi @kunalverma2512 yes sure, I will work on all of the issues! According to your preference either before merging or after merging, I'll make sure to fix the issues tomorrow.

@kunalverma2512

Copy link
Copy Markdown
Owner

@coderabbitai please make 3 separate issues for HIGH category, ALL MEDIUM category and ALL LOW category issues mentioned by coderabbitai in its recent comment and make sure you write a very insane and detailed description about each issues that why its needed and what the problem and how things are working without this work and how it will resolve issue and where it improves codebase, understanding and in terms of industry standards and reality

also write few learnings about the concepts needed to be learned by contributor during working on this and what concepts,topics to go through before working on this PR so that contributor can increase his/her learnings and become more better developer

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="kunalverma2512/CodeLens"
assignee="kunalverma2512"
pr_url="https://github.com/kunalverma2512/CodeLens/pull/269"
review_url="https://github.com/kunalverma2512/CodeLens/pull/269#issuecomment-4934958050"

high_body="$(mktemp)"
medium_body="$(mktemp)"
low_body="$(mktemp)"
trap 'rm -f "$high_body" "$medium_body" "$low_body"' EXIT

cat >"$high_body" <<EOF
## Summary

Make the Codeforces contest synchronization scheduler safe when the backend runs in more than one process or deployment instance.

This follow-up tracks the **High**-severity finding from the production review of `#269`: the scheduler created in \`server/jobs/contestSync.js\` runs inside every application process. The current implementation is correct for a single server process, but it has no leader-election or distributed-lock mechanism.

**Backlinks**
- Source PR: $pr_url
- Source review: $review_url
- Requested by: @$assignee

## Current behavior

On successful database connection, \`server/server.js\` calls \`startContestSyncJob()\`. That function:
1. performs an initial Codeforces sync; and
2. registers a \`node-cron\` task for the top of every hour.

Because this happens in-process, each independently running server instance registers and executes its own schedule. Examples include PM2 cluster mode, container replicas, autoscaling hosts, rolling deployments with overlap, or multiple dynos.

The persistence writes are largely idempotent because contest records are upserted, so this is not expected to corrupt data immediately. However, idempotency does not make duplicated work safe or desirable at production scale.

## Why this is needed

A horizontally scalable web application must not assume that a scheduled task is executed by only one process. Without coordination, a deployment with N replicas produces N initial syncs and N hourly syncs at the same time.

### Production impact without this change

- **External API pressure:** every replica calls the public Codeforces API at the same cadence, multiplying outbound traffic and increasing the chance of rate limiting or temporary blocks.
- **Database pressure:** every replica performs the same bulk upserts and stale-reminder cleanup, producing avoidable write load and lock contention.
- **Operational noise:** logs report multiple “synced” executions for one intended schedule, complicating incident investigation and monitoring.
- **Scaling regression:** increasing availability by adding instances unintentionally increases background-work volume instead of keeping it constant.
- **Future correctness risk:** today’s sync is mostly idempotent; a later non-idempotent side effect (email, push notification, analytics event, cleanup) added to the job could become duplicated immediately.

This is a standard distributed-systems concern: an in-memory scheduler coordinates only one process, not a fleet.

## Scope and affected areas

- \`server/jobs/contestSync.js\`
- \`server/server.js\`
- Potentially a small persistence abstraction/model for job locks or job-run leases
- Potentially configuration/deployment documentation for the selected scheduling strategy
- Automated tests for lock/lease acquisition and release behavior

## Required changes

Choose and document one production-appropriate single-execution strategy. Suitable approaches include:

1. **Dedicated scheduler/worker deployment**
   - Run the sync only from one explicitly configured worker process or managed platform scheduler.
   - Keep web instances free of cron ownership.

2. **Database-backed lease / distributed lock**
   - Atomically acquire a short-lived lease before starting a sync.
   - Only the lock holder performs the work.
   - Use an expiry/TTL or lease timestamp so a crashed process cannot block future syncs indefinitely.
   - Release or expire the lease safely after the run.

3. **Queue-based repeatable job**
   - Use a queue system with a documented single scheduler/worker ownership model.

The implementation must avoid a check-then-act race. For example, “read lock document, then insert it” is not sufficient under concurrent startup; lease acquisition must be atomic at the database/queue layer. Preserve the current startup sync and hourly refresh intent, but ensure only one actor executes each logical run.

Also define observable behavior:
- log whether an instance acquired or skipped the lease;
- log failures with enough context to distinguish lock acquisition from Codeforces API/sync failure;
- avoid holding a lock forever if a process crashes;
- ensure a slow sync cannot overlap with another run unintentionally.

## Acceptance criteria

- [ ] Starting two or more backend instances results in **one** logical contest sync per scheduled interval.
- [ ] The initial sync is similarly protected from concurrent process startup.
- [ ] Lock/leader acquisition is atomic and safe against concurrent attempts.
- [ ] A crash, timeout, or abandoned lock cannot permanently prevent later syncs.
- [ ] A long-running sync cannot cause unintended concurrent work from another instance.
- [ ] The approach is documented in code/configuration so deployment owners know which process owns scheduled work.
- [ ] Logs/metrics make it possible to tell whether a process ran or intentionally skipped a scheduled sync.
- [ ] Tests cover concurrent acquisition, lease expiry/recovery, successful sync, failed sync, and skipped execution.
- [ ] The existing single-instance development workflow remains simple and functional.

## Suggested validation scenarios

1. Launch two application processes against the same database and verify that only one makes the Codeforces request for the same interval.
2. Simulate a process terminating after acquiring the lock and verify another instance can run after the lease expires.
3. Simulate a Codeforces/API failure and confirm the next scheduled run is not permanently suppressed.
4. Simulate a sync taking longer than the normal interval and verify the chosen policy prevents unsafe overlap.
5. Verify normal one-instance startup still runs exactly one initial synchronization.

## Contributor learning guide

Before implementation, review these topics:

- **Distributed locks and leader election:** why process-local state is insufficient in a replicated deployment.
- **Leases versus mutexes:** leases expire and recover from crashes; understand clock/TTL implications.
- **Atomic compare-and-set operations:** MongoDB conditional updates, unique constraints, and why “find then create” races.
- **At-least-once execution and idempotency:** scheduled jobs may run more than once; design writes and side effects accordingly.
- **Job observability:** structured logs, run IDs, duration/error metrics, and diagnosing skipped versus failed work.
- **Deployment topology:** PM2 cluster mode, containers, autoscaling, rolling releases, and managed cron/worker services.
- **Failure-mode design:** API outage, database outage, process crash, slow work, and overlapping schedules.

## Definition of done

The contest sync is safe to deploy across multiple backend replicas without multiplying scheduled work, recovers automatically from failed owners, and is covered by focused automated tests and clear operational documentation.
EOF

cat >"$medium_body" <<EOF
## Summary

Harden the contest tracker’s lifecycle handling, reminder data flow, and filtering behavior by resolving the five **Medium**-severity findings from the production review of `#269`.

**Backlinks**
- Source PR: $pr_url
- Source review: $review_url
- Requested by: @$assignee

## Goals

The tracker should display an accurate contest lifecycle, avoid avoidable per-tab network/database work, remain correct after delayed synchronization, prevent duplicate in-app reminders across browser tabs where feasible, and expose every backend-derived division/category in the UI.

## Findings and required work

### 1. Persist system-testing lifecycle phases so contests do not remain falsely “live”

**Affected area:** \`server/modules/contests/service.js\`, especially \`ContestService.syncCodeforcesContests()\`; downstream repository/frontend display behavior.

#### Current behavior

The sync selection persists only \`BEFORE\`, \`CODING\`, and a bounded group of \`FINISHED\` contests. Codeforces contests can transition through phases such as \`PENDING_SYSTEM_TEST\` and \`SYSTEM_TEST\` after submissions close and before they become \`FINISHED\`.

When those intermediate phases are excluded, the cached document can retain its previous \`CODING\` phase. The frontend treats \`CODING\` as running, so it can show “Live Now” even though the contest is no longer accepting submissions.

#### Production impact

Users receive inaccurate real-time status. A contest can look active for up to the next successful sync—or longer if combined with the finished-window issue below. This undermines the core promise of a live contest tracker and can cause users to set expectations around a contest that has ended.

#### Required change

Persist all Codeforces phases that are necessary to transition an already cached upcoming/running contest to a non-runnable state. Define a clear UI/domain mapping for intermediate testing phases—e.g., show “System testing” or a non-live completed state—and ensure upcoming queries and reminder eligibility match that mapping.

---

### 2. Eliminate duplicate polling of active reminders within one authenticated tab

**Affected area:** \`frontend/src/components/contests/ContestReminderBell.jsx\`, \`frontend/src/components/shared/ContestReminderNotifier.jsx\`, \`frontend/src/hooks/useContests.js\`, and possibly a shared context/provider or query cache.

#### Current behavior

The navbar bell polls \`getMyActiveReminders()\` every 60 seconds, while the global reminder notifier independently polls the same endpoint every 30 seconds. Both are mounted on authenticated pages.

#### Production impact

Each active browser tab produces duplicate network requests, authentication work, and database reads for identical information. As user traffic grows, this costs capacity without improving freshness and creates two independent sources of state that can disagree momentarily.

#### Required change

Introduce one shared reminder-data owner per browser tab (for example, a context/provider, a dedicated hook with a shared cache, or an existing application query layer). The bell and notifier should consume the same cached state. Preserve cleanup on auth changes/unmounts and define a single polling/refetch cadence appropriate for reminder urgency.

---

### 3. Ensure delayed or bursty syncs cannot leave contests permanently stale

**Affected area:** \`server/modules/contests/service.js\` and relevant repository queries/tests.

#### Current behavior

Only the 20 most recently started \`FINISHED\` contests are selected for persistence on each sync. Under normal hourly operation, a contest usually gets recorded as finished. But after downtime, API failure, a burst of contest events, or an unusual data ordering, a previously cached \`BEFORE\`/\`CODING\` contest may fall outside that 20-item window before its \`FINISHED\` phase is ever persisted.

#### Production impact

A stale document may remain eligible for the “upcoming/running” query indefinitely, showing users an event that has ended and retaining stale reminders. This is a data-lifecycle correctness issue, not merely a display delay.

#### Required change

Make phase reconciliation complete for records the application already tracks. Options include:
- persist the entire relevant Codeforces response when reasonably bounded; or
- explicitly update every locally cached non-finished contest from the latest API response, regardless of a historical finished-contest display/cache cap.

Keep any retention cap separate from lifecycle reconciliation. If old finished records need pruning, perform that as an explicit retention policy rather than relying on a limited fetch subset to communicate state transitions.

---

### 4. Coordinate reminder toasts across browser tabs

**Affected area:** \`frontend/src/components/shared/ContestReminderNotifier.jsx\`; potentially reminder API semantics and cross-tab browser coordination.

#### Current behavior

The notifier’s \`shownThisSession\` set exists only inside one mounted component instance. If the same authenticated user has multiple tabs open, both can fetch a reminder with \`notifiedAt: null\` before either fire-and-forget \`markReminderNotified()\` request completes. Both tabs can show the toast.

#### Production impact

Users can receive duplicate “starting soon” notifications. The impact is mostly user-experience noise, but reminders are a core feature and duplicate alerts diminish trust in their reliability. The risk grows for users who keep dashboards/trackers open in multiple tabs.

#### Required change

Provide cross-tab coordination where supported, such as \`BroadcastChannel\` or a carefully scoped \`localStorage\` event/key, while retaining server-side \`notifiedAt\` as the durable source of truth. Consider making the server’s mark-notified operation expose clear idempotent/conditional semantics so clients can tell whether they won the notification claim. The implementation must degrade safely when browser storage/channel APIs are unavailable.

---

### 5. Make all backend-generated division/category values reachable in the UI

**Affected area:** \`server/modules/contests/service.js\` (\`parseDivision\`) and \`frontend/src/components/contests/UpcomingContestsList.jsx\` (\`DIVISIONS\` and filtering).

#### Current behavior

The backend can classify contests into values such as \`Div. 1 + 2\`, \`Kotlin Heroes\`, \`ICPC\`, and \`Other\`. The UI’s explicit filter list includes only \`Div. 1\` through \`Div. 4\`, \`Educational\`, and \`Global\`.

#### Production impact

Contests in the omitted categories are displayed only under the unfiltered “all” view and cannot be selected through a dedicated category filter. This is inconsistent behavior between the API’s domain vocabulary and the UI’s navigation model.

#### Required change

Establish one canonical category vocabulary or mapping contract. Either include every backend value in the filter UI, deliberately group them into documented categories, or generate filters from returned data while preserving a stable and accessible order. Make sure filtering labels, badge labels, and empty states remain understandable on small screens.

## Acceptance criteria

- [ ] Intermediate Codeforces testing phases are persisted and never shown as “Live Now” after submissions close.
- [ ] Cached contests that are already known locally are reliably reconciled to their terminal phase after outages, delayed runs, or high event volume.
- [ ] Reminder bell and global notifier use one shared active-reminder data flow per authenticated tab, with no parallel duplicate polling loops.
- [ ] The shared polling mechanism has correct cancellation/cleanup on logout, unmount, and request replacement.
- [ ] Multiple open tabs do not normally show duplicate reminder toasts for the same contest; fallback behavior is safe and documented.
- [ ] Reminder notification remains one-time and durable across reloads through the backend’s \`notifiedAt\` state.
- [ ] Every category produced by backend parsing is either filterable or intentionally mapped into an explicit visible grouping.
- [ ] Frontend loading, error, empty, and unauthenticated states still work correctly.
- [ ] Tests cover each item below.

## Test plan

### Backend/service and repository tests
- Phase transitions: \`BEFORE → CODING → PENDING_SYSTEM_TEST/SYSTEM_TEST → FINISHED\`.
- A locally cached running contest that is absent from the “latest 20 finished” window is still reconciled to finished.
- Reminder eligibility and active-reminder queries for all lifecycle states.
- API response and ordering edge cases, including delayed syncs and temporary Codeforces failures.

### Frontend tests
- Exactly one request owner/cadence is used when both bell and notifier render.
- Logout/unmount cancels polling and does not update stale component state.
- Bell count and toast use the same reminder snapshot.
- Two simulated tabs coordinate notification display; the losing tab suppresses its duplicate toast.
- Every parsed division/category is represented or grouped correctly by the UI filter.

## Contributor learning guide

Review these topics before starting:

- **State-machine modeling:** model contest lifecycle phases explicitly rather than treating state as a few independent strings.
- **Cache reconciliation:** distinguish data retention/display limits from authoritative state synchronization.
- **Client-side data ownership:** shared query caches, context providers, subscription lifecycles, polling deduplication, and stale-while-revalidate patterns.
- **React effect correctness:** dependency arrays, cancellation, stale async responses, interval cleanup, and avoiding duplicated side effects.
- **Cross-tab web coordination:** \`BroadcastChannel\`, \`storage\` events, atomicity limitations, and progressive enhancement.
- **Distributed/idempotent notifications:** server-side durable state, conditional updates, and why fire-and-forget client requests can race.
- **Domain contracts:** keep backend enum/parser outputs and frontend filters/types synchronized.
- **User-facing time/status semantics:** distinguish “running,” “ended,” and “system testing” instead of deriving status only from timestamps.

## Definition of done

The tracker reports the correct contest lifecycle, performs no duplicate reminder polling within a tab, remains correct after synchronization gaps, avoids multi-tab duplicate alerts in supported browsers, and presents a complete consistent division/category filter model with automated coverage.
EOF

cat >"$low_body" <<EOF
## Summary

Improve validation consistency, index hygiene, React async cleanup, accessibility, and automated coverage for the contest tracker introduced in `#269`.

This issue consolidates the **Low**-severity findings from the production review. None is expected to block a single-instance release independently, but resolving them increases maintainability, accessibility, performance clarity, and confidence in future changes.

**Backlinks**
- Source PR: $pr_url
- Source review: $review_url
- Requested by: @$assignee

## Findings and required work

### 1. Use a consistent validation strategy for reminder route parameters

**Affected area:** \`server/modules/contests/controller.js\`, \`server/modules/contests/routes.js\`, \`server/modules/contests/validation.js\`.

#### Current behavior

\`POST /reminders\` validates its body through the shared Zod \`validate()\` middleware. In contrast, \`DELETE /reminders/:contestId\` and \`POST /reminders/:contestId/notified\` manually call \`parseInt\` in controller methods and only reject \`NaN\`.

#### Why improve this

The current behavior is functional for common inputs, but validation rules are split across layers. \`parseInt\` has permissive parsing behavior for strings such as \`"12abc"\`, and the two parameter routes do not enforce the same positive-integer contract as creation.

A unified schema prevents divergent error payloads and makes future validation changes (bounds, integer-only semantics, reusable error messages) safer.

#### Required change

Create/reuse a Zod schema and middleware for \`req.params.contestId\`, apply it at the route boundary, and have controllers consume the normalized validated value. Maintain consistent 400 error responses across reminder endpoints.

---

### 2. Review and remove redundant contest indexes

**Affected area:** \`server/models/Contest.js\`, database migration/index-management procedure.

#### Current behavior

The model has a standalone \`contestId\` index and a compound unique index on \`{ platform, contestId }\`. With only the \`codeforces\` platform currently permitted, these indexes substantially overlap for the known query patterns.

#### Why improve this

Every extra MongoDB index consumes disk/RAM and must be updated on inserts/upserts. Redundant indexes increase write amplification and operational overhead. Indexes should correspond to actual query predicates/sorts, not merely be added defensively.

#### Required change

Inspect repository query shapes and MongoDB index usage. Retain only indexes justified by current queries and anticipated multi-platform requirements. If the standalone index is unnecessary, remove it using the project’s safe index deployment process; do not assume changing the schema automatically drops an existing production index.

Document the selected index/query rationale.

---

### 3. Prevent stale async hook requests from updating state after unmount or replacement

**Affected area:** \`frontend/src/hooks/useContests.js\`.

#### Current behavior

The hook’s contest/reminder fetch functions update React state after awaiting requests, but do not use cancellation/sequence guards. The bell and notifier components already employ cancellation flags, so behavior is inconsistent.

#### Why improve this

React 18 no longer always emits the historical warning for post-unmount state updates, but stale responses can still overwrite newer state after auth changes, navigation, retry/refetch, or slow network requests. Guarding request ownership makes the hook deterministic and prevents needless work.

#### Required change

Use an appropriate cancellation/identity strategy—such as \`AbortController\` support in the API client, a mounted flag plus request sequence token, or a query library—so only the latest active request can update state. Ensure interval cleanup and errors follow the same guard.

---

### 4. Provide an accessible name for the contest reminder bell control

**Affected area:** \`frontend/src/components/contests/ContestReminderBell.jsx\`.

#### Current behavior

The icon-only reminder-bell link relies on \`title\` text.

#### Why improve this

A tooltip title is not a reliable accessible name for screen-reader users and may not be consistently exposed across assistive technology. Icon-only interactive elements need an explicit programmatic label, and a visible/semantic indication of the badge count where useful.

#### Required change

Add a descriptive \`aria-label\` to the link/button, including the number of due reminders when applicable. Mark decorative icon SVGs appropriately and verify keyboard focus and contrast remain clear.

---

### 5. Add automated coverage for the new contest/reminder feature

**Affected area:** all newly introduced contest backend/frontend modules, plus the existing test configuration and CI scripts.

#### Current behavior

The feature adds models, routes, validation, controllers, services, repositories, a cron job, a frontend service, hook, countdown, list, bell, notifier, widget, and page/layout integrations without automated tests.

#### Why improve this

The feature’s most important behavior is time-, lifecycle-, authorization-, and async-dependent. Manual testing is valuable but cannot reliably protect phase transitions, duplicate-reminder prevention, polling cleanup, validation contracts, or future refactors. Tests turn the feature’s expected behavior into an executable contract for contributors.

#### Required change

Add a proportionate test suite aligned with existing project tooling. Avoid introducing a second testing stack if the repository already has one. Prioritize behavior over implementation details.

## Acceptance criteria

- [ ] All reminder endpoints enforce a single positive-integer \`contestId\` contract at the route boundary and return consistent validation errors.
- [ ] Contest indexes are justified by documented query patterns; unused/redundant production indexes are safely removed or retained with rationale.
- [ ] \`useContests\` does not apply results from an unmounted, cancelled, or superseded request.
- [ ] The icon-only reminder navigation control has an explicit accessible name and communicates its state/count appropriately.
- [ ] The project contains focused automated tests for the contest feature, integrated into the established test command/CI path.
- [ ] Existing behavior remains backward compatible for anonymous users, authenticated users, empty data, failures, and normal navigation.

## Minimum recommended test coverage

### Backend
- Zod validation for valid IDs, zero, negatives, decimals, non-numeric strings, and permissive-looking strings such as \`"12abc"\`.
- Route integration tests confirming authentication requirements and response shapes.
- \`ContestService\` tests for add/remove/mark reminder behavior, missing contest handling, invalid lifecycle state handling, and sync normalization.
- Repository tests for unique reminder idempotency, upcoming sorting/filtering, active reminder joins, and stale-reminder cleanup.
- Scheduler tests for startup invocation/error logging boundaries where feasible.

### Frontend
- \`useContests\` tests for optimistic add/remove and rollback on request failure.
- Tests that slow/stale fetches cannot overwrite newer data or update after unmount.
- \`ContestCountdown\` tests for upcoming, running, ended, and multi-day displays.
- \`UpcomingContestsList\` tests for loading/error/empty/filter/auth redirect states.
- Reminder bell accessibility tests: accessible name, count state, keyboard navigation.
- Notifier tests for one-time notification, dismissal, and mark-notified request behavior.

## Contributor learning guide

Before implementation, study:

- **Input validation at trust boundaries:** schema validation, coercion pitfalls, and why \`parseInt\` is not full input validation.
- **REST API consistency:** route-level contracts, normalized error payloads, and controller responsibilities.
- **MongoDB index design:** compound index ordering, selectivity, query plans, write amplification, \`explain()\`, and safe index migrations.
- **React async effects:** AbortController, stale-response races, cleanup patterns, and request sequencing.
- **Web accessibility:** accessible names, ARIA usage for icon-only controls, focus management, and testing with role/name queries.
- **Testing strategy:** unit versus integration versus component tests; fake timers for countdown/polling; API mocking; database isolation; and testing observable behavior rather than private implementation.
- **Regression prevention:** every fixed bug or clarified contract should get a focused test where practical.

## Definition of done

The contest module has consistent input validation, intentional database indexing, safe async client state handling, accessible icon controls, and an automated test baseline that protects the feature’s core lifecycle, reminder, and UI behavior.
EOF

high_url=$(gh issue create --repo "$repo" \
  --title "High: Make Codeforces contest sync single-execution safe across multiple backend instances" \
  --body-file "$high_body" \
  --assignee "$assignee")

medium_url=$(gh issue create --repo "$repo" \
  --title "Medium: Harden contest lifecycle reconciliation, reminder polling, and category filtering" \
  --body-file "$medium_body" \
  --assignee "$assignee")

low_url=$(gh issue create --repo "$repo" \
  --title "Low: Improve contest validation, index hygiene, async cleanup, accessibility, and test coverage" \
  --body-file "$low_body" \
  --assignee "$assignee")

printf 'HIGH=%s\nMEDIUM=%s\nLOW=%s\n' "$high_url" "$medium_url" "$low_url"

Length of output: 27790


@kunalverma2512 Created and assigned the three detailed follow-up issues:

  1. High: #277 — Make Codeforces contest sync single-execution safe across multiple backend instances
  2. Medium: #278 — Harden contest lifecycle reconciliation, reminder polling, and category filtering
  3. Low: #279 — Improve contest validation, index hygiene, async cleanup, accessibility, and test coverage

Each issue includes the rationale, current behavior, real production impact, affected modules, actionable requirements, acceptance criteria, recommended test coverage, and a contributor learning guide covering the relevant engineering concepts.

@kunalverma2512

Copy link
Copy Markdown
Owner

Hi @kunalverma2512 yes sure, I will work on all of the issues! According to your preference either before merging or after merging, I'll make sure to fix the issues tomorrow.

Amazing @ida-jemi
I will merge this PR now and I am also creating separate issues so that you can work afterwards

TIP : @ida-jemi since your work is so much good and adds value and also a source for more growth to see in future thats why I think if you can make a blog plus a good message in community that how you worked, what challenges you faced and how you adopted this challenging environment , what lessons you got by working on this issue and special topics , concepts you learned while contributing

NOTE : PLEASE UPDATE YOU WORK ON OUT MATRIX CHANNEL SO AS TO MAKE COMMUNITY ACTIVE, STRONGER AND FULL OF CURIOSITY AND A BETTER DEVELOPER COMMUNITY

@ida-jemi

Copy link
Copy Markdown
Contributor Author

@kunalverma2512 Thank you so much!!
I'll try to contribute as much as possible and keep it updated on the matrix channel.

@kunalverma2512
kunalverma2512 self-requested a review July 10, 2026 19:01
@kunalverma2512 kunalverma2512 added the enhancement New feature or request label Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Upcoming Contests Tracker for Codeforces with In-App Reminders

2 participants