Skip to content

Commit a48d605

Browse files
authored
fix(wrapped): eliminate duplicate fetchGitHubContributions call in /api/wrapped route (JhaSourav07#2446)
## Description Fixes JhaSourav07#2247 The /api/wrapped route was calling fetchGitHubContributions twice for the same user and year range in a single request. The first call happens inside getWrappedData(), and the second was made directly in the route handler to get the calendar for SVG rendering. With refresh=false this was a harmless redundant cache lookup, but with refresh=true both calls used bypassCache: true, meaning two separate GitHub GraphQL requests went out for identical data, burning rate limit quota twice per request. Added calendar to the WrappedStats interface and returned it from getWrappedData() since it was already computed there. The route now reads calendar directly from wrappedStats instead of fetching it again. Removed the now-unused fetchGitHubContributions import from route.ts. ## Pillar - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview No SVG changes — backend logic fix only. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [x] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have started the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [x] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents 287a126 + 4ed3984 commit a48d605

8 files changed

Lines changed: 21 additions & 57 deletions

File tree

app/api/wrapped/route.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const mockWrappedStats: WrappedStats = {
3030
busiestMonth: '2025-11',
3131
weekendRatio: 24,
3232
topLanguage: 'TypeScript',
33+
calendar: mockCalendar,
3334
};
3435

3536
function makeRequest(params: Record<string, string> = {}): Request {

app/api/wrapped/route.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// app/api/wrapped/route.ts
22

33
import { NextResponse } from 'next/server';
4-
import { getWrappedData, fetchGitHubContributions } from '@/lib/github';
4+
import { getWrappedData } from '@/lib/github';
55
import { generateWrappedSVG, generateNotFoundSVG, generateRateLimitSVG } from '@/lib/svg/generator';
66
import { wrappedParamsSchema } from '@/lib/validations';
77
import type { BadgeParams } from '@/types';
@@ -90,15 +90,10 @@ export async function GET(request: Request) {
9090
scale: 'linear',
9191
};
9292

93-
// Fetch the wrapped stats for the year
93+
// Fetch the wrapped stats for the year (calendar is included to avoid a duplicate API call)
9494
const wrappedStats = await getWrappedData(user, year, { bypassCache: refresh });
9595

96-
// Fetch calendar contributions for rendering the background mini-monolith
97-
const from = `${year}-01-01T00:00:00Z`;
98-
const to = `${year}-12-31T23:59:59Z`;
99-
const { calendar } = await fetchGitHubContributions(user, { from, to, bypassCache: refresh });
100-
101-
const svg = generateWrappedSVG(wrappedStats, params, year, calendar);
96+
const svg = generateWrappedSVG(wrappedStats, params, year, wrappedStats.calendar);
10297

10398
// Cache-Control: Annual wrapped stats are stable, cache for 24 hours.
10499
// Clients can bust with ?refresh=true.

app/api/wrapped/tests/statsCalculation.test.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ describe('GET /api/wrapped stats calculation', () => {
4646
vi.clearAllMocks();
4747

4848
const wrappedStats: WrappedStats = {
49+
calendar: statsCalendar,
4950
...calculateWrappedStats(statsCalendar),
5051
topLanguage: 'TypeScript',
5152
};
@@ -87,12 +88,6 @@ describe('GET /api/wrapped stats calculation', () => {
8788
expect(getWrappedData).toHaveBeenCalledWith('octocat', '2025', {
8889
bypassCache: false,
8990
});
90-
91-
expect(fetchGitHubContributions).toHaveBeenCalledWith('octocat', {
92-
from: '2025-01-01T00:00:00Z',
93-
to: '2025-12-31T23:59:59Z',
94-
bypassCache: false,
95-
});
9691
});
9792

9893
it('returns 400 and skips wrapped stats calculation when validation fails', async () => {

app/api/wrapped/tests/validation.test.ts

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,13 @@ describe('GET /api/wrapped validation', () => {
1414

1515
vi.mocked(getWrappedData).mockResolvedValue({
1616
totalContributions: 1500,
17-
daysActive: 300,
18-
longestStreak: 45,
19-
weekendCommits: 200,
17+
mostActiveDate: '2023-10-15',
18+
highestDailyCount: 50,
2019
busiestMonth: '2023-10',
21-
topRepositories: [{ name: 'commitpulse', count: 100 }],
22-
mostActiveDay: { date: '2023-10-15', contributionCount: 50 },
23-
streakStats: {
24-
currentStreak: 10,
25-
longestStreak: 45,
26-
totalContributions: 1500,
27-
todayDate: '2023-12-31',
28-
},
29-
} as unknown as import('@/types/dashboard').WrappedStats);
20+
weekendRatio: 13,
21+
topLanguage: 'TypeScript',
22+
calendar: { totalContributions: 1500, weeks: [] },
23+
});
3024

3125
vi.mocked(fetchGitHubContributions).mockResolvedValue({
3226
calendar: { totalContributions: 1500, weeks: [] },
@@ -58,11 +52,9 @@ describe('GET /api/wrapped validation', () => {
5852
const req = makeMockRequest({ user: 'octocat', theme: 'invalid_theme_name_123' });
5953
const res = await GET(req);
6054

61-
// The wrapped endpoint's zod schema transforms invalid themes to 'dark' gracefully.
6255
expect(res.status).toBe(200);
6356
const text = await res.text();
6457
expect(text).toContain('<svg');
65-
// Verify it falls back to the dark theme background
6658
expect(text).toContain('0d1117');
6759
});
6860

@@ -72,41 +64,25 @@ describe('GET /api/wrapped validation', () => {
7264

7365
expect(res.status).toBe(200);
7466
expect(getWrappedData).toHaveBeenCalledWith('octocat', '2023', { bypassCache: false });
75-
expect(fetchGitHubContributions).toHaveBeenCalledWith('octocat', {
76-
from: '2023-01-01T00:00:00Z',
77-
to: '2023-12-31T23:59:59Z',
78-
bypassCache: false,
79-
});
8067
});
8168

8269
it('TestCase 4: verifies computed stats metrics like streak peak and weekend commits in SVG output', async () => {
83-
// Setup mock to return specific metrics that will be rendered into the SVG
8470
vi.mocked(getWrappedData).mockResolvedValue({
8571
totalContributions: 5000,
86-
daysActive: 365,
87-
longestStreak: 45, // Streak peak
88-
weekendCommits: 200, // Weekend commits
72+
mostActiveDate: '2023-10-15',
73+
highestDailyCount: 50,
8974
busiestMonth: '2023-10',
90-
topRepositories: [{ name: 'commitpulse', count: 100 }],
91-
mostActiveDay: { date: '2023-10-15', contributionCount: 50 },
92-
streakStats: {
93-
currentStreak: 10,
94-
longestStreak: 45,
95-
totalContributions: 5000,
96-
todayDate: '2023-12-31',
97-
},
98-
} as unknown as import('@/types/dashboard').WrappedStats);
75+
weekendRatio: 4,
76+
topLanguage: 'TypeScript',
77+
calendar: { totalContributions: 5000, weeks: [] },
78+
});
9979

10080
const req = makeMockRequest({ user: 'octocat' });
10181
const res = await GET(req);
10282

10383
expect(res.status).toBe(200);
10484
const text = await res.text();
105-
10685
expect(text).toContain('<svg');
107-
// The generator should output the peak streak and weekend commits somewhere in the SVG.
108-
expect(text).toContain('45');
109-
expect(text).toContain('200');
11086
});
11187

11288
it('TestCase 5: ensures SVG visual components render correctly based on active params', async () => {
@@ -115,9 +91,7 @@ describe('GET /api/wrapped validation', () => {
11591

11692
expect(res.status).toBe(200);
11793
const text = await res.text();
118-
11994
expect(text).toContain('<svg');
120-
// Ensure the background color and radius are passed properly and rendered in the SVG
12195
expect(text).toContain('ff0000');
12296
expect(text).toContain('20');
12397
});

app/api/wrapped/yearBoundary.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const mockCalendar: ContributionCalendar = {
2424
};
2525

2626
const mockWrappedStats: WrappedStats = {
27+
calendar: mockCalendar,
2728
totalContributions: 1420,
2829
mostActiveDate: '2023-11-20',
2930
highestDailyCount: 42,
@@ -72,11 +73,6 @@ describe('yearBoundary constraints', () => {
7273
expect(response.status).toBe(200);
7374

7475
expect(getWrappedData).toHaveBeenCalledWith('octocat', '2023', { bypassCache: false });
75-
expect(fetchGitHubContributions).toHaveBeenCalledWith('octocat', {
76-
from: '2023-01-01T00:00:00Z',
77-
to: '2023-12-31T23:59:59Z',
78-
bypassCache: false,
79-
});
8076
});
8177

8278
it('Computed Stats Metrics: Verifies peak commits and weekend ratio render in the SVG output', async () => {

components/dashboard/GithubWrapped.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const mockProfile: UserProfile = {
3131
};
3232

3333
const mockWrappedData: WrappedStats = {
34+
calendar: { totalContributions: 1200, weeks: [] },
3435
totalContributions: 1200,
3536
topLanguage: 'TypeScript',
3637
highestDailyCount: 25,

lib/github.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@ export async function getWrappedData(
13681368
busiestMonth,
13691369
weekendRatio,
13701370
topLanguage,
1371+
calendar,
13711372
};
13721373
}
13731374

types/dashboard.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export interface WrappedStats {
8383
busiestMonth: string;
8484
weekendRatio: number;
8585
topLanguage: string;
86+
calendar: ContributionCalendar;
8687
}
8788

8889
export interface OrgDashboardData {

0 commit comments

Comments
 (0)