Updated zod validation on date range.
Some checks are pending
Create docker images (cloud) / Build, push, and deploy (push) Waiting to run
Node.js CI / build (push) Waiting to run

This commit is contained in:
Mike Cao 2026-02-08 22:58:25 -08:00
parent 6c793325e2
commit fb6fd293fb
10 changed files with 36 additions and 31 deletions

View file

@ -11,7 +11,7 @@
}, },
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev -p 3002 --turbo", "dev": "next dev -p 3009 --turbo",
"build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app", "build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app",
"start": "next start", "start": "next start",
"build-docker": "npm-run-all build-db build-tracker build-geo build-app", "build-docker": "npm-run-all build-db build-tracker build-geo build-app",

View file

@ -1,7 +1,6 @@
import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { dateRangeParams, filterParams } from '@/lib/schema'; import { filterParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getWebsiteEventStats } from '@/queries/sql/events/getWebsiteEventStats'; import { getWebsiteEventStats } from '@/queries/sql/events/getWebsiteEventStats';
@ -9,8 +8,7 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
...dateRangeParams,
...filterParams, ...filterParams,
}); });

View file

@ -1,9 +1,8 @@
import JSZip from 'jszip'; import JSZip from 'jszip';
import Papa from 'papaparse'; import Papa from 'papaparse';
import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { dateRangeParams, pagingParams } from '@/lib/schema'; import { pagingParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getEventMetrics, getPageviewMetrics, getSessionMetrics } from '@/queries/sql'; import { getEventMetrics, getPageviewMetrics, getSessionMetrics } from '@/queries/sql';
@ -11,8 +10,7 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
...dateRangeParams,
...pagingParams, ...pagingParams,
}); });

View file

@ -2,7 +2,7 @@ import { z } from 'zod';
import { EVENT_COLUMNS, EVENT_TYPE, SESSION_COLUMNS } from '@/lib/constants'; import { EVENT_COLUMNS, EVENT_TYPE, SESSION_COLUMNS } from '@/lib/constants';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { badRequest, json, unauthorized } from '@/lib/response'; import { badRequest, json, unauthorized } from '@/lib/response';
import { dateRangeParams, filterParams, searchParams } from '@/lib/schema'; import { filterParams, searchParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { import {
getChannelExpandedMetrics, getChannelExpandedMetrics,
@ -15,11 +15,10 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
type: z.string(), type: z.string(),
limit: z.coerce.number().optional(), limit: z.coerce.number().optional(),
offset: z.coerce.number().optional(), offset: z.coerce.number().optional(),
...dateRangeParams,
...searchParams, ...searchParams,
...filterParams, ...filterParams,
}); });

View file

@ -2,7 +2,7 @@ import { z } from 'zod';
import { EVENT_COLUMNS, EVENT_TYPE, SESSION_COLUMNS } from '@/lib/constants'; import { EVENT_COLUMNS, EVENT_TYPE, SESSION_COLUMNS } from '@/lib/constants';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { badRequest, json, unauthorized } from '@/lib/response'; import { badRequest, json, unauthorized } from '@/lib/response';
import { dateRangeParams, filterParams, searchParams } from '@/lib/schema'; import { filterParams, searchParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { import {
getChannelMetrics, getChannelMetrics,
@ -15,11 +15,10 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
type: z.string(), type: z.string(),
limit: z.coerce.number().optional(), limit: z.coerce.number().optional(),
offset: z.coerce.number().optional(), offset: z.coerce.number().optional(),
...dateRangeParams,
...searchParams, ...searchParams,
...filterParams, ...filterParams,
}); });

View file

@ -1,8 +1,7 @@
import { z } from 'zod';
import { getCompareDate } from '@/lib/date'; import { getCompareDate } from '@/lib/date';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { dateRangeParams, filterParams } from '@/lib/schema'; import { filterParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getPageviewStats, getSessionStats } from '@/queries/sql'; import { getPageviewStats, getSessionStats } from '@/queries/sql';
@ -10,8 +9,7 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
...dateRangeParams,
...filterParams, ...filterParams,
}); });

View file

@ -1,7 +1,6 @@
import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { dateRangeParams, filterParams, pagingParams, searchParams } from '@/lib/schema'; import { filterParams, pagingParams, searchParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getWebsiteSessions } from '@/queries/sql'; import { getWebsiteSessions } from '@/queries/sql';
@ -9,8 +8,7 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
...dateRangeParams,
...filterParams, ...filterParams,
...pagingParams, ...pagingParams,
...searchParams, ...searchParams,

View file

@ -1,8 +1,7 @@
import { z } from 'zod';
import { getCompareDate } from '@/lib/date'; import { getCompareDate } from '@/lib/date';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { dateRangeParams, filterParams } from '@/lib/schema'; import { filterParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getWebsiteStats } from '@/queries/sql'; import { getWebsiteStats } from '@/queries/sql';
@ -10,8 +9,7 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
...dateRangeParams,
...filterParams, ...filterParams,
}); });

View file

@ -1,8 +1,7 @@
import { z } from 'zod';
import { EVENT_COLUMNS, FILTER_COLUMNS, SEGMENT_TYPES, SESSION_COLUMNS } from '@/lib/constants'; import { EVENT_COLUMNS, FILTER_COLUMNS, SEGMENT_TYPES, SESSION_COLUMNS } from '@/lib/constants';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { badRequest, json, unauthorized } from '@/lib/response'; import { badRequest, json, unauthorized } from '@/lib/response';
import { dateRangeParams, fieldsParam, searchParams } from '@/lib/schema'; import { fieldsParam, searchParams, withDateRange } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getWebsiteSegments } from '@/queries/prisma'; import { getWebsiteSegments } from '@/queries/prisma';
import { getValues } from '@/queries/sql'; import { getValues } from '@/queries/sql';
@ -11,9 +10,8 @@ export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const schema = withDateRange({
type: fieldsParam, type: fieldsParam,
...dateRangeParams,
...searchParams, ...searchParams,
}); });

View file

@ -23,6 +23,25 @@ export const dateRangeParams = {
compare: z.enum(['prev', 'yoy']).optional(), compare: z.enum(['prev', 'yoy']).optional(),
}; };
export function withDateRange<T extends z.ZodRawShape>(shape?: T) {
return z
.object({
...dateRangeParams,
...shape,
})
.superRefine((data: Record<string, unknown>, ctx) => {
const hasTimestamps = data.startAt != null && data.endAt != null;
const hasDates = data.startDate != null && data.endDate != null;
if (!hasTimestamps && !hasDates) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Either startAt+endAt or startDate+endDate must be provided',
});
}
});
}
export const filterParams = { export const filterParams = {
path: z.string().optional(), path: z.string().optional(),
referrer: z.string().optional(), referrer: z.string().optional(),