From dc23467d67813d1ec30effe73ef7317483c7e179 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 2 Mar 2026 13:21:14 -0800 Subject: [PATCH] add event-data GET route --- .../[websiteId]/event-data/[eventId]/route.ts | 4 +- .../websites/[websiteId]/event-data/route.ts | 35 ++++++++ src/queries/sql/events/getEventData.ts | 88 ++++++++++++------- src/queries/sql/events/getEventDataById.ts | 63 +++++++++++++ src/queries/sql/index.ts | 1 + 5 files changed, 157 insertions(+), 34 deletions(-) create mode 100644 src/app/api/websites/[websiteId]/event-data/route.ts create mode 100644 src/queries/sql/events/getEventDataById.ts diff --git a/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts b/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts index 54afab214..e640f1b6f 100644 --- a/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/[eventId]/route.ts @@ -1,7 +1,7 @@ import { parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { canViewWebsite } from '@/permissions'; -import { getEventData } from '@/queries/sql/events/getEventData'; +import { getEventDataById } from '@/queries/sql/events/getEventDataById'; export async function GET( request: Request, @@ -19,7 +19,7 @@ export async function GET( return unauthorized(); } - const data = await getEventData(websiteId, eventId); + const data = await getEventDataById(websiteId, eventId); return json(data); } diff --git a/src/app/api/websites/[websiteId]/event-data/route.ts b/src/app/api/websites/[websiteId]/event-data/route.ts new file mode 100644 index 000000000..5bc36fa4d --- /dev/null +++ b/src/app/api/websites/[websiteId]/event-data/route.ts @@ -0,0 +1,35 @@ +import { z } from 'zod'; +import { getQueryFilters, parseRequest } from '@/lib/request'; +import { json, unauthorized } from '@/lib/response'; +import { filterParams } from '@/lib/schema'; +import { canViewWebsite } from '@/permissions'; +import { getEventData } from '@/queries/sql/events/getEventData'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ websiteId: string }> }, +) { + const schema = z.object({ + startAt: z.coerce.number().int(), + endAt: z.coerce.number().int(), + ...filterParams, + }); + + const { auth, query, error } = await parseRequest(request, schema); + + if (error) { + return error(); + } + + const { websiteId } = await params; + + if (!(await canViewWebsite(auth, websiteId))) { + return unauthorized(); + } + + const filters = await getQueryFilters(query, websiteId); + + const data = await getEventData(websiteId, filters); + + return json(data); +} diff --git a/src/queries/sql/events/getEventData.ts b/src/queries/sql/events/getEventData.ts index f12c95ca0..d6cefbda6 100644 --- a/src/queries/sql/events/getEventData.ts +++ b/src/queries/sql/events/getEventData.ts @@ -1,63 +1,87 @@ -import type { EventData } from '@/generated/prisma/client'; import clickhouse from '@/lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; import prisma from '@/lib/prisma'; +import type { QueryFilters } from '@/lib/types'; const FUNCTION_NAME = 'getEventData'; -export async function getEventData( - ...args: [websiteId: string, eventId: string] -): Promise { +export async function getEventData(...args: [websiteId: string, filters: QueryFilters]) { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), }); } -async function relationalQuery(websiteId: string, eventId: string) { - const { rawQuery } = prisma; +async function relationalQuery(websiteId: string, filters: QueryFilters) { + const { rawQuery, parseFilters } = prisma; + const { filterQuery, cohortQuery, joinSessionQuery, queryParams } = parseFilters({ + ...filters, + websiteId, + }); return rawQuery( ` - select event_data.website_id as "websiteId", - event_data.website_event_id as "eventId", - website_event.event_name as "eventName", - event_data.data_key as "dataKey", - event_data.string_value as "stringValue", - event_data.number_value as "numberValue", - event_data.date_value as "dateValue", - event_data.data_type as "dataType", - event_data.created_at as "createdAt" + select + event_data.website_id as "websiteId", + event_data.website_event_id as "eventId", + website_event.event_name as "eventName", + event_data.data_key as "dataKey", + event_data.string_value as "stringValue", + event_data.number_value as "numberValue", + event_data.date_value as "dateValue", + event_data.data_type as "dataType", + event_data.created_at as "createdAt" from event_data join website_event on website_event.event_id = event_data.website_event_id and website_event.website_id = {{websiteId::uuid}} + and website_event.created_at between {{startDate}} and {{endDate}} + ${cohortQuery} + ${joinSessionQuery} where event_data.website_id = {{websiteId::uuid}} - and event_data.website_event_id = {{eventId::uuid}} + and event_data.created_at between {{startDate}} and {{endDate}} + ${filterQuery} + order by event_data.created_at desc + limit 1000 `, - { websiteId, eventId }, + queryParams, FUNCTION_NAME, ); } -async function clickhouseQuery(websiteId: string, eventId: string): Promise { - const { rawQuery } = clickhouse; +async function clickhouseQuery(websiteId: string, filters: QueryFilters) { + const { rawQuery, parseFilters } = clickhouse; + const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, websiteId }); return rawQuery( ` - select website_id as websiteId, - event_id as eventId, - event_name as eventName, - data_key as dataKey, - string_value as stringValue, - number_value as numberValue, - date_value as dateValue, - data_type as dataType, - created_at as createdAt - from event_data - where website_id = {websiteId:UUID} - and event_id = {eventId:UUID} + select + website_id as websiteId, + event_id as eventId, + event_name as eventName, + data_key as dataKey, + string_value as stringValue, + number_value as numberValue, + date_value as dateValue, + data_type as dataType, + created_at as createdAt + from event_data + any left join ( + select * + from website_event + where website_id = {websiteId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_type = 2) website_event + on website_event.event_id = event_data.event_id + and website_event.session_id = event_data.session_id + and website_event.website_id = event_data.website_id + ${cohortQuery} + where event_data.website_id = {websiteId:UUID} + and event_data.created_at between {startDate:DateTime64} and {endDate:DateTime64} + ${filterQuery} + order by event_data.created_at desc + limit 1000 `, - { websiteId, eventId }, + queryParams, FUNCTION_NAME, ); } diff --git a/src/queries/sql/events/getEventDataById.ts b/src/queries/sql/events/getEventDataById.ts new file mode 100644 index 000000000..8abd58088 --- /dev/null +++ b/src/queries/sql/events/getEventDataById.ts @@ -0,0 +1,63 @@ +import type { EventData } from '@/generated/prisma/client'; +import clickhouse from '@/lib/clickhouse'; +import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db'; +import prisma from '@/lib/prisma'; + +const FUNCTION_NAME = 'getEventDataById'; + +export async function getEventDataById( + ...args: [websiteId: string, eventId: string] +): Promise { + return runQuery({ + [PRISMA]: () => relationalQuery(...args), + [CLICKHOUSE]: () => clickhouseQuery(...args), + }); +} + +async function relationalQuery(websiteId: string, eventId: string) { + const { rawQuery } = prisma; + + return rawQuery( + ` + select event_data.website_id as "websiteId", + event_data.website_event_id as "eventId", + website_event.event_name as "eventName", + event_data.data_key as "dataKey", + event_data.string_value as "stringValue", + event_data.number_value as "numberValue", + event_data.date_value as "dateValue", + event_data.data_type as "dataType", + event_data.created_at as "createdAt" + from event_data + join website_event on website_event.event_id = event_data.website_event_id + and website_event.website_id = {{websiteId::uuid}} + where event_data.website_id = {{websiteId::uuid}} + and event_data.website_event_id = {{eventId::uuid}} + `, + { websiteId, eventId }, + FUNCTION_NAME, + ); +} + +async function clickhouseQuery(websiteId: string, eventId: string): Promise { + const { rawQuery } = clickhouse; + + return rawQuery( + ` + select website_id as websiteId, + event_id as eventId, + event_name as eventName, + data_key as dataKey, + string_value as stringValue, + number_value as numberValue, + date_value as dateValue, + data_type as dataType, + created_at as createdAt + from event_data + where website_id = {websiteId:UUID} + and event_id = {eventId:UUID} + `, + { websiteId, eventId }, + FUNCTION_NAME, + ); +} diff --git a/src/queries/sql/index.ts b/src/queries/sql/index.ts index 1573bdefd..e914104a7 100644 --- a/src/queries/sql/index.ts +++ b/src/queries/sql/index.ts @@ -1,3 +1,4 @@ +export * from './events/getEventDataById'; export * from './events/getEventDataEvents'; export * from './events/getEventDataFields'; export * from './events/getEventDataProperties';