Skip to content

Commit b979b44

Browse files
committed
test(validation): add timezone boundary validation
1 parent 535fd0f commit b979b44

3 files changed

Lines changed: 19 additions & 7 deletions

File tree

app/api/stats/route.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,22 @@ export async function GET(request: Request) {
2424
const parseResult = statsParamsSchema.safeParse(Object.fromEntries(searchParams.entries()));
2525

2626
if (!parseResult.success) {
27+
const details = parseResult.error.flatten();
28+
29+
if (details.fieldErrors.tz?.length) {
30+
return NextResponse.json(
31+
{
32+
error: 'Invalid "tz" parameter',
33+
details,
34+
},
35+
{ status: 400 }
36+
);
37+
}
38+
2739
return NextResponse.json(
2840
{
2941
error: 'Invalid parameters',
30-
details: parseResult.error.flatten(),
42+
details,
3143
},
3244
{ status: 400 }
3345
);

app/api/streak/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export async function GET(request: Request) {
9494
disable_particles,
9595
glow,
9696
} = parseResult.data;
97-
97+
const normalizedView = view as 'default' | 'monthly' | 'heatmap' | 'pulse';
9898
const themeName = theme || 'dark';
9999
const from = customFrom
100100
? new Date(customFrom).toISOString()
@@ -146,7 +146,7 @@ export async function GET(request: Request) {
146146
hideBackground: hide_background,
147147
hide_stats,
148148
lang,
149-
view,
149+
view: normalizedView,
150150
delta_format,
151151
width,
152152
height,
@@ -195,17 +195,17 @@ export async function GET(request: Request) {
195195
}
196196

197197
let svg = '';
198-
if (view === 'monthly') {
198+
if (normalizedView === 'monthly') {
199199
const stats = calculateMonthlyStats(
200200
calendar,
201201
timezone,
202202
getMonthlyReferenceDate(year, timezone)
203203
);
204204
svg = generateMonthlySVG(stats, params);
205-
} else if (view === 'heatmap') {
205+
} else if (normalizedView === 'heatmap') {
206206
const stats = calculateStreak(calendar, timezone, undefined, grace);
207207
svg = generateHeatmapSVG(stats, params, calendar);
208-
} else if (view === 'pulse') {
208+
} else if (normalizedView === 'pulse') {
209209
// We still use calculateStreak here to efficiently parse totalContributions for the stat display,
210210
// even though the sparkline generator will extract its own daily 30-day timeline below.
211211
const stats = calculateStreak(calendar, timezone, undefined, grace);

lib/validations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ const baseStreakParamsSchema = z.object({
202202
lang: z.enum(supportedLanguages).catch('en').default('en'),
203203
tz: timeZoneParam,
204204
// Unknown view values fall back to the default dashboard view.
205-
view: z.enum(['default', 'monthly']).catch('default').default('default'),
205+
view: z.enum(['default', 'monthly', 'heatmap', 'pulse']).catch('default').default('default'),
206206
// Invalid delta formats fall back to percentage mode.
207207
delta_format: z.enum(['percent', 'absolute', 'both']).catch('percent').default('percent'),
208208
width: dimensionParam('width', 100, 1200),

0 commit comments

Comments
 (0)