diff --git a/src/app/api/metrics/contributions/route.ts b/src/app/api/metrics/contributions/route.ts index a0c2ab894..17a294e64 100644 --- a/src/app/api/metrics/contributions/route.ts +++ b/src/app/api/metrics/contributions/route.ts @@ -17,10 +17,6 @@ interface ContributionResponse { data: Record; } -function toLocalDateStr(d: Date): string { - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; -} - function mergeContributionDays( a: Record, b: Record @@ -39,7 +35,7 @@ async function fetchContributionsForAccount( ): Promise { const since = new Date(); since.setDate(since.getDate() - days); - const sinceStr = toLocalDateStr(since); + const sinceStr = since.toISOString().slice(0, 10); const searchRes = await fetch( `${GITHUB_API}/search/commits?q=author:${githubLogin}+author-date:>=${sinceStr}&per_page=100&sort=author-date&order=desc`, diff --git a/src/app/api/metrics/prs/route.ts b/src/app/api/metrics/prs/route.ts index fb28df2ba..67aff864f 100644 --- a/src/app/api/metrics/prs/route.ts +++ b/src/app/api/metrics/prs/route.ts @@ -34,11 +34,11 @@ async function fetchPRMetrics(token: string): Promise { const data = (await searchRes.json()) as { total_count: number; - items: Array<{ state: string; created_at: string; closed_at: string | null }>; + items: Array<{ state: string; created_at: string; closed_at: string | null; pull_request?: { merged_at: string | null } }>; }; const open = data.items.filter((pr) => pr.state === "open").length; - const merged = data.items.filter((pr) => pr.state === "closed").length; + const merged = data.items.filter((pr) => pr.pull_request?.merged_at != null).length; const closedPRs = data.items.filter((pr) => pr.closed_at); const avgReviewMs = diff --git a/src/app/api/metrics/repos/route.ts b/src/app/api/metrics/repos/route.ts index 23f6c06ac..dd4ac922c 100644 --- a/src/app/api/metrics/repos/route.ts +++ b/src/app/api/metrics/repos/route.ts @@ -71,6 +71,42 @@ async function fetchReposForAccount( repoMap[name] = (repoMap[name] ?? 0) + 1; } + const linkHeader = searchRes.headers.get("link"); + const pagePattern = /<[^>]*[?&]page=(\d+)[^>]*>;\s*rel="last"/; + const lastPageMatch = linkHeader?.match(pagePattern); + if (lastPageMatch) { + const lastPage = parseInt(lastPageMatch[1], 10); + const pagePromises: Promise[] = []; + for (let page = 2; page <= Math.min(lastPage, 5); page++) { + pagePromises.push( + fetch( + `${GITHUB_API}/search/commits?q=author:${githubLogin}+author-date:>=${sinceStr}&per_page=100&page=${page}&sort=author-date&order=desc`, + { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + }, + cache: "no-store", + } + ) + ); + } + const pageResults = await Promise.allSettled(pagePromises); + for (const res of pageResults) { + if (res.status !== "fulfilled" || !res.value.ok) continue; + const pageData = (await res.value.json()) as { + items: Array<{ + repository: { full_name: string; html_url: string }; + commit: { author: { date: string } }; + }>; + }; + for (const item of pageData.items) { + const name = item.repository.full_name; + repoMap[name] = (repoMap[name] ?? 0) + 1; + } + } + } + const repos = Object.entries(repoMap) .map(([name, commits]) => ({ name, commits })) .sort((a, b) => b.commits - a.commits) diff --git a/src/app/api/metrics/streak/route.ts b/src/app/api/metrics/streak/route.ts index ae35df78f..f74928d37 100644 --- a/src/app/api/metrics/streak/route.ts +++ b/src/app/api/metrics/streak/route.ts @@ -49,6 +49,38 @@ async function fetchActiveDates( activeDates.add(item.commit.author.date.slice(0, 10)); } + const linkHeader = searchRes.headers.get("link"); + const pagePattern = /<[^>]*[?&]page=(\d+)[^>]*>;\s*rel="last"/; + const lastPageMatch = linkHeader?.match(pagePattern); + if (lastPageMatch) { + const lastPage = parseInt(lastPageMatch[1], 10); + const pagePromises: Promise[] = []; + for (let page = 2; page <= Math.min(lastPage, 5); page++) { + pagePromises.push( + fetch( + `${GITHUB_API}/search/commits?q=author:${githubLogin}+author-date:>=${sinceStr}&per_page=100&page=${page}&sort=author-date&order=desc`, + { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + }, + cache: "no-store", + } + ) + ); + } + const pageResults = await Promise.allSettled(pagePromises); + for (const res of pageResults) { + if (res.status !== "fulfilled" || !res.value.ok) continue; + const pageData = (await res.value.json()) as { + items: Array<{ commit: { author: { date: string } } }>; + }; + for (const item of pageData.items) { + activeDates.add(item.commit.author.date.slice(0, 10)); + } + } + } + return activeDates; } diff --git a/src/components/StreakTracker.tsx b/src/components/StreakTracker.tsx index 141e32275..95a4d77c5 100644 --- a/src/components/StreakTracker.tsx +++ b/src/components/StreakTracker.tsx @@ -394,8 +394,8 @@ interface StreakCalendarProps { onMonthChange: (date: Date) => void; } -function toLocalDateStr(d: Date): string { - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; +function toUtcDateStr(d: Date): string { + return d.toISOString().slice(0, 10); } function StreakCalendar({ contributions, currentMonth, onMonthChange }: StreakCalendarProps) { @@ -463,10 +463,10 @@ function StreakCalendar({ contributions, currentMonth, onMonthChange }: StreakCa return
; } - const dateStr = toLocalDateStr(dayData.date); + const dateStr = toUtcDateStr(dayData.date); const commitCount = contributions[dateStr] ?? 0; const isFuture = dayData.date > today; - const isToday = dayData.date.toDateString() === today.toDateString(); + const isToday = toUtcDateStr(dayData.date) === toUtcDateStr(today); let bgColor = "bg-white dark:bg-transparent"; let borderColor = "border border-[var(--border)]";