From ec7042636a95139a51bbf217c1c47151e72a2de3 Mon Sep 17 00:00:00 2001 From: Aditya8369 Date: Mon, 1 Jun 2026 09:50:19 +0530 Subject: [PATCH] fix: Streak tracker silently breaks for streaks longer than 90 days --- src/app/api/leaderboard/route.ts | 3 ++- src/app/api/metrics/streak/route.ts | 21 +++++++++------------ src/lib/public-profile-data.ts | 5 ++--- src/lib/streak.ts | 7 +++++++ 4 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 src/lib/streak.ts diff --git a/src/app/api/leaderboard/route.ts b/src/app/api/leaderboard/route.ts index ab97f9697..a147a8ab1 100644 --- a/src/app/api/leaderboard/route.ts +++ b/src/app/api/leaderboard/route.ts @@ -17,6 +17,7 @@ import { upstashRateLimitFixedWindow, upstashTryAcquireLock, } from "@/lib/upstash-rest"; +import { getStreakLookbackStart } from "@/lib/streak"; export const dynamic = "force-dynamic"; @@ -265,7 +266,7 @@ async function buildLeaderboard(): Promise { const now = new Date(); const monthStart = toDateStr(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))); - const streakStart = toDateStr(new Date(Date.now() - 90 * 86400000)); + const streakStart = toDateStr(getStreakLookbackStart()); const safeUsers = (users ?? []) as PublicUser[]; diff --git a/src/app/api/metrics/streak/route.ts b/src/app/api/metrics/streak/route.ts index 363909a41..b3a137ccc 100644 --- a/src/app/api/metrics/streak/route.ts +++ b/src/app/api/metrics/streak/route.ts @@ -12,6 +12,7 @@ import { import { supabaseAdmin } from "@/lib/supabase"; import { resolveAppUser } from "@/lib/resolve-user"; import { dateDiffDays, toDateStr } from "@/lib/dateUtils"; +import { getStreakLookbackStart } from "@/lib/streak"; export const dynamic = "force-dynamic"; @@ -34,11 +35,9 @@ async function fetchActiveDates( ttlSeconds: METRICS_CACHE_TTL_SECONDS.streak, }, async () => { - // Look back 90 days — the maximum window GitHub's Commit Search supports. - // Requesting beyond 90 days will silently return fewer results. - const since = new Date(); - since.setDate(since.getDate() - 90); - const sinceStr = since.toISOString().slice(0, 10); // "YYYY-MM-DD" + // Look back far enough to cover realistic streaks without truncating + // long-running runs. GitHub's commit search supports date filters up to a year. + const sinceStr = getStreakLookbackStart().toISOString().slice(0, 10); // "YYYY-MM-DD" const activeDates = new Set(); let page = 1; @@ -48,7 +47,7 @@ async function fetchActiveDates( // • Unauthenticated: 10 requests/minute // // This loop pages through up to 10 pages (1,000 commits max) to cover - // the full 90-day window. Each page = 1 request against the 30 req/min quota. + // the configured look-back window. Each page = 1 request against the 30 req/min quota. // Most users need only 1–2 pages; the cap of 10 prevents runaway API usage // for extremely active accounts. while (true) { @@ -172,8 +171,8 @@ function calculateStreakFromDates( current: currentStreak, longest: longestStreak, lastCommitDate: lastDay, - // totalActiveDays counts only days with real commits or freezes in the 90-day window, - // not the full streak length — useful for the "active days" stat on the dashboard. + // totalActiveDays counts only days with real commits or freezes in the configured + // look-back window, not the full streak length — useful for the "active days" stat. totalActiveDays: commitDays.length, freezeDates: Array.from(freezeDates), }; @@ -201,12 +200,10 @@ export async function GET(req: NextRequest) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } - // Fetch streak freeze dates from Supabase for the past 90 days. + // Fetch streak freeze dates from Supabase for the same look-back window as commits. // These are merged with commit dates so a freeze day doesn't break the streak. // Only fetched when the user has a Supabase row (appUserId is non-null). - const since = new Date(); - since.setDate(since.getDate() - 90); - const sinceStr = since.toISOString().slice(0, 10); + const sinceStr = getStreakLookbackStart().toISOString().slice(0, 10); const freezeDates = new Set(); if (appUserId) { diff --git a/src/lib/public-profile-data.ts b/src/lib/public-profile-data.ts index 8a924f032..8da64f6f5 100644 --- a/src/lib/public-profile-data.ts +++ b/src/lib/public-profile-data.ts @@ -1,4 +1,5 @@ import { dateDiffDays, toDateStr } from "@/lib/dateUtils"; +import { getStreakLookbackStart } from "@/lib/streak"; import type { GitHubAchievement } from "@/lib/github-achievements"; import { syncGitHubAchievementsForUser } from "@/lib/github-achievements"; import { fetchPinnedRepoDetails, type PinnedRepoDetails } from "@/lib/pinned-repos"; @@ -121,9 +122,7 @@ export async function fetchPublicStreak( username: string, token?: string ): Promise { - const since = new Date(); - since.setDate(since.getDate() - 90); - const sinceStr = since.toISOString().slice(0, 10); + const sinceStr = getStreakLookbackStart().toISOString().slice(0, 10); const res = await ghFetch( `${GITHUB_API}/search/commits?q=author:${username}+author-date:>=${sinceStr}&per_page=100&sort=author-date&order=desc`, diff --git a/src/lib/streak.ts b/src/lib/streak.ts new file mode 100644 index 000000000..43d6675e4 --- /dev/null +++ b/src/lib/streak.ts @@ -0,0 +1,7 @@ +export const STREAK_LOOKBACK_DAYS = 365; + +export function getStreakLookbackStart(referenceDate = new Date()): Date { + const since = new Date(referenceDate); + since.setDate(since.getDate() - STREAK_LOOKBACK_DAYS); + return since; +} \ No newline at end of file