diff --git a/package.json b/package.json index d162be63a..6e0645b5f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "type": "module", "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", "start": "next start", "build-docker": "npm-run-all build-db build-tracker build-geo build-app", diff --git a/src/app/api/websites/[websiteId]/events/stats/route.ts b/src/app/api/websites/[websiteId]/events/stats/route.ts index 61e151d4a..6d0a04602 100644 --- a/src/app/api/websites/[websiteId]/events/stats/route.ts +++ b/src/app/api/websites/[websiteId]/events/stats/route.ts @@ -1,7 +1,6 @@ -import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { dateRangeParams, filterParams } from '@/lib/schema'; +import { filterParams, withDateRange } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getWebsiteEventStats } from '@/queries/sql/events/getWebsiteEventStats'; @@ -9,8 +8,7 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - ...dateRangeParams, + const schema = withDateRange({ ...filterParams, }); diff --git a/src/app/api/websites/[websiteId]/export/route.ts b/src/app/api/websites/[websiteId]/export/route.ts index eec81c6d4..1beb9bcb2 100644 --- a/src/app/api/websites/[websiteId]/export/route.ts +++ b/src/app/api/websites/[websiteId]/export/route.ts @@ -1,9 +1,8 @@ import JSZip from 'jszip'; import Papa from 'papaparse'; -import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { dateRangeParams, pagingParams } from '@/lib/schema'; +import { pagingParams, withDateRange } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getEventMetrics, getPageviewMetrics, getSessionMetrics } from '@/queries/sql'; @@ -11,8 +10,7 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - ...dateRangeParams, + const schema = withDateRange({ ...pagingParams, }); diff --git a/src/app/api/websites/[websiteId]/metrics/expanded/route.ts b/src/app/api/websites/[websiteId]/metrics/expanded/route.ts index d52c17736..34ebcd598 100644 --- a/src/app/api/websites/[websiteId]/metrics/expanded/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/expanded/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { EVENT_COLUMNS, EVENT_TYPE, SESSION_COLUMNS } from '@/lib/constants'; import { getQueryFilters, parseRequest } from '@/lib/request'; 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 { getChannelExpandedMetrics, @@ -15,11 +15,10 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ + const schema = withDateRange({ type: z.string(), limit: z.coerce.number().optional(), offset: z.coerce.number().optional(), - ...dateRangeParams, ...searchParams, ...filterParams, }); diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 12784adbe..c9649ae9a 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { EVENT_COLUMNS, EVENT_TYPE, SESSION_COLUMNS } from '@/lib/constants'; import { getQueryFilters, parseRequest } from '@/lib/request'; 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 { getChannelMetrics, @@ -15,11 +15,10 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ + const schema = withDateRange({ type: z.string(), limit: z.coerce.number().optional(), offset: z.coerce.number().optional(), - ...dateRangeParams, ...searchParams, ...filterParams, }); diff --git a/src/app/api/websites/[websiteId]/pageviews/route.ts b/src/app/api/websites/[websiteId]/pageviews/route.ts index af59bce46..b9945c87b 100644 --- a/src/app/api/websites/[websiteId]/pageviews/route.ts +++ b/src/app/api/websites/[websiteId]/pageviews/route.ts @@ -1,8 +1,7 @@ -import { z } from 'zod'; import { getCompareDate } from '@/lib/date'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { dateRangeParams, filterParams } from '@/lib/schema'; +import { filterParams, withDateRange } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getPageviewStats, getSessionStats } from '@/queries/sql'; @@ -10,8 +9,7 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - ...dateRangeParams, + const schema = withDateRange({ ...filterParams, }); diff --git a/src/app/api/websites/[websiteId]/sessions/route.ts b/src/app/api/websites/[websiteId]/sessions/route.ts index ed4757a1c..88199aff9 100644 --- a/src/app/api/websites/[websiteId]/sessions/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/route.ts @@ -1,7 +1,6 @@ -import { z } from 'zod'; import { getQueryFilters, parseRequest } from '@/lib/request'; 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 { getWebsiteSessions } from '@/queries/sql'; @@ -9,8 +8,7 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - ...dateRangeParams, + const schema = withDateRange({ ...filterParams, ...pagingParams, ...searchParams, diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 9d21f4f55..bb060e41f 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -1,8 +1,7 @@ -import { z } from 'zod'; import { getCompareDate } from '@/lib/date'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { dateRangeParams, filterParams } from '@/lib/schema'; +import { filterParams, withDateRange } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getWebsiteStats } from '@/queries/sql'; @@ -10,8 +9,7 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - ...dateRangeParams, + const schema = withDateRange({ ...filterParams, }); diff --git a/src/app/api/websites/[websiteId]/values/route.ts b/src/app/api/websites/[websiteId]/values/route.ts index 172325e3f..f07e60f3d 100644 --- a/src/app/api/websites/[websiteId]/values/route.ts +++ b/src/app/api/websites/[websiteId]/values/route.ts @@ -1,8 +1,7 @@ -import { z } from 'zod'; import { EVENT_COLUMNS, FILTER_COLUMNS, SEGMENT_TYPES, SESSION_COLUMNS } from '@/lib/constants'; import { getQueryFilters, parseRequest } from '@/lib/request'; 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 { getWebsiteSegments } from '@/queries/prisma'; import { getValues } from '@/queries/sql'; @@ -11,9 +10,8 @@ export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ + const schema = withDateRange({ type: fieldsParam, - ...dateRangeParams, ...searchParams, }); diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 020ed0c25..197acea35 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -23,6 +23,25 @@ export const dateRangeParams = { compare: z.enum(['prev', 'yoy']).optional(), }; +export function withDateRange(shape?: T) { + return z + .object({ + ...dateRangeParams, + ...shape, + }) + .superRefine((data: Record, 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 = { path: z.string().optional(), referrer: z.string().optional(),