Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/aakash811/Student-Progress-Tracker/llms.txt

Use this file to discover all available pages before exploring further.

SkillSync keeps student data current by fetching it directly from the Codeforces public API. Syncs run automatically every day via an external cron trigger, and can also be started manually per student or for all students at once. A Redis caching layer sits in front of every Codeforces API call to reduce external requests and keep the dashboard fast during reads.

What gets synced

Every sync run fetches and stores the following fields for each student:
FieldSourceDescription
currRatinguser.infoStudent’s current Codeforces rating
rankuser.infoCodeforces rank label (e.g. “Expert”)
maxRatinguser.infoHighest rating the student has ever reached
lastSyncedAtSystem clockTimestamp set at the end of a successful sync
contestDatauser.ratingComplete contest rating history
submissionsuser.statusUp to 10,000 most recent submissions
lastActiveAtDerivedDate of the most recent accepted (OK) submission

Automated daily sync

SkillSync uses cron-job.org to trigger a daily sync. The external scheduler sends a POST request to:
POST /cron/sync
The request must include the x-cron-secret header matching the CRON_SECRET environment variable. If the secret is missing or incorrect, the endpoint returns 401 Unauthorized and no sync runs. When authenticated, the handler calls runCodeforcesSync(), which iterates over every student in MongoDB:
1

Invalidate cache

Redis keys cf:info:handle, cf:contest:handle, and cf:submissions:handle are deleted so the next fetch always goes to the Codeforces API.
2

Fetch fresh data

user.info, user.rating, and user.status (up to 10,000 submissions) are fetched in parallel from Codeforces.
3

Update MongoDB

The student document is updated with the new rating, rank, max rating, contest history, submissions, lastSyncedAt, and lastActiveAt.
4

Check inactivity

If the student has been inactive for 7 or more days and has not yet received an inactivity email this streak, a reminder email is sent and emailRemindersSent is incremented.

Manual sync options

Per-student sync

Each row in the student table has a sync icon button. Clicking it calls:
GET /api/codeforces/sync/:handle
This invalidates the Redis cache for that handle, fetches fresh data from Codeforces, and updates the student’s MongoDB document. The button shows a spinner while the request is in flight and a brief confirmation state on success.

Sync All

The Sync All button in the dashboard toolbar triggers a bulk sync:
POST /cron/sync-all
This runs the same runCodeforcesSync() loop as the automated daily sync but without requiring the cron secret header. The UI shows a “Background sync started…” banner immediately and then refreshes the student list after a short delay to give the async operation time to complete.
On Render’s free tier, syncing a large number of students can take several seconds per student due to cold-start latency and Codeforces API rate limits. The endpoint returns 200 OK immediately with { "message": "Sync started" } and runs the sync asynchronously so the request does not time out.

Cache invalidation on sync

Before fetching from Codeforces, every sync call explicitly deletes three Redis keys for the student’s handle:
await Promise.all([
  redis.del(`cf:info:${handle}`),
  redis.del(`cf:contest:${handle}`),
  redis.del(`cf:submissions:${handle}`),
]);
This guarantees that the subsequent fetch always retrieves live data rather than a stale cached response.

Redis caching layer

After fetching from Codeforces, responses are written to Upstash Redis with the following TTLs:
Data typeCache keyTTL
User profilecf:info:handle6 hours
Contest historycf:contest:handle6 hours
Submissionscf:submissions:handle2 hours
Contest problem countscf:contest-problems:contestId7 days
Full contest stats responsecf:contest-history:handle:days12 hours
During dashboard reads (stats pages, contest history), the service checks Redis first. A cache hit avoids a Codeforces API call entirely. A cache miss fetches live data, writes it to Redis, then returns it to the caller.

Inactivity tracking

lastActiveAt is computed at the end of every sync by finding the most recent submission where verdict === "OK" and converting its Unix timestamp to a JavaScript Date. If no accepted submission exists, lastActiveAt is set to null. The number of inactive days is then:
const inactiveDays = lastActiveAt
  ? (Date.now() - lastActiveAt.getTime()) / (1000 * 60 * 60 * 24)
  : Infinity;
When inactiveDays >= 7, the sync loop checks whether an inactivity email should be sent. See Automated inactivity email alerts for the full trigger logic.

Build docs developers (and LLMs) love