From 14f3db550bb6705b1c5e4b92409ea0d5eb2ff3a4 Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Tue, 11 Nov 2025 10:32:31 +0100 Subject: [PATCH 1/3] Use raw query with on conflict in createSession --- src/app/api/send/route.ts | 1 + src/queries/sql/sessions/createSession.ts | 75 ++++++++++++----------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index f5e00c8a..2c2085bf 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -146,6 +146,7 @@ export async function POST(request: Request) { region, city, distinctId: id, + createdAt, }); } diff --git a/src/queries/sql/sessions/createSession.ts b/src/queries/sql/sessions/createSession.ts index 958754f1..b5106a54 100644 --- a/src/queries/sql/sessions/createSession.ts +++ b/src/queries/sql/sessions/createSession.ts @@ -1,41 +1,44 @@ import { Prisma } from '@/generated/prisma/client'; import prisma from '@/lib/prisma'; -export async function createSession(data: Prisma.SessionCreateInput) { - const { - id, - websiteId, - browser, - os, - device, - screen, - language, - country, - region, - city, - distinctId, - } = data; +const FUNCTION_NAME = 'createSession'; - try { - return await prisma.client.session.create({ - data: { - id, - websiteId, - browser, - os, - device, - screen, - language, - country, - region, - city, - distinctId, - }, - }); - } catch (e: any) { - if (e.message.toLowerCase().includes('unique constraint')) { - return null; - } - throw e; - } +export async function createSession(data: Prisma.SessionCreateInput) { + const { rawQuery } = prisma; + + await rawQuery( + ` + insert into session ( + session_id, + website_id, + browser, + os, + device, + screen, + language, + country, + region, + city, + distinct_id, + created_at + ) + values ( + {{id}}, + {{websiteId}}, + {{browser}}, + {{os}}, + {{device}}, + {{screen}}, + {{language}}, + {{country}}, + {{region}}, + {{city}}, + {{distinctId}}, + {{createdAt}} + ) + on conflict (session_id) do nothing + `, + data, + FUNCTION_NAME, + ); } From 30781430c5ef51c6d4d79b620ffa4f34293bf9d4 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 11 Nov 2025 13:13:25 -0800 Subject: [PATCH 2/3] remove timezone from realtime. Closes #3700 --- src/app/api/realtime/[websiteId]/route.ts | 8 +------- src/components/hooks/queries/useRealtimeQuery.ts | 6 ++---- src/lib/clickhouse.ts | 2 +- src/queries/sql/pageviews/getPageviewStats.ts | 2 +- src/queries/sql/sessions/getSessionStats.ts | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/app/api/realtime/[websiteId]/route.ts b/src/app/api/realtime/[websiteId]/route.ts index 054a1241..eaa0bbd8 100644 --- a/src/app/api/realtime/[websiteId]/route.ts +++ b/src/app/api/realtime/[websiteId]/route.ts @@ -1,21 +1,15 @@ import { REALTIME_RANGE } from '@/lib/constants'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { timezoneParam } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getRealtimeData } from '@/queries/sql'; import { startOfMinute, subMinutes } from 'date-fns'; -import z from 'zod'; export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - timezone: timezoneParam, - }); - - const { auth, query, error } = await parseRequest(request, schema); + const { auth, query, error } = await parseRequest(request); if (error) { return error(); diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index 582fe9fa..ed388b03 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,4 +1,3 @@ -import { useTimezone } from '@/components/hooks/useTimezone'; import { REALTIME_INTERVAL } from '@/lib/constants'; import { useApi } from '../useApi'; @@ -24,11 +23,10 @@ export interface RealtimeData { export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); - const { timezone } = useTimezone(); const { data, isLoading, error } = useQuery({ - queryKey: ['realtime', { websiteId, timezone }], + queryKey: ['realtime', { websiteId }], queryFn: async () => { - return get(`/realtime/${websiteId}`, { timezone }); + return get(`/realtime/${websiteId}`); }, enabled: !!websiteId, refetchInterval: REALTIME_INTERVAL, diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 5e6e7133..4880e122 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -61,7 +61,7 @@ function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) { function getDateSQL(field: string, unit: string, timezone?: string) { if (timezone) { - return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'), '${timezone}')`; + return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'))`; } return `toDateTime(date_trunc('${unit}', ${field}))`; } diff --git a/src/queries/sql/pageviews/getPageviewStats.ts b/src/queries/sql/pageviews/getPageviewStats.ts index 7dd9ac93..a6619e87 100644 --- a/src/queries/sql/pageviews/getPageviewStats.ts +++ b/src/queries/sql/pageviews/getPageviewStats.ts @@ -45,7 +45,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise<{ x: string; y: number }[]> { - const { timezone = 'utc', unit = 'day' } = filters; + const { timezone = 'UTC', unit = 'day' } = filters; const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, diff --git a/src/queries/sql/sessions/getSessionStats.ts b/src/queries/sql/sessions/getSessionStats.ts index 07582d39..ea93b226 100644 --- a/src/queries/sql/sessions/getSessionStats.ts +++ b/src/queries/sql/sessions/getSessionStats.ts @@ -45,7 +45,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise<{ x: string; y: number }[]> { - const { timezone = 'utc', unit = 'day' } = filters; + const { timezone = 'UTC', unit = 'day' } = filters; const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, From bf498d92392571fbf025011c5d30627c5d30ff42 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 11 Nov 2025 13:45:41 -0800 Subject: [PATCH 3/3] add RealtimeData to types --- .../hooks/queries/useRealtimeQuery.ts | 21 +------------------ src/lib/types.ts | 20 ++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index ed388b03..9e20da04 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,25 +1,6 @@ import { REALTIME_INTERVAL } from '@/lib/constants'; import { useApi } from '../useApi'; - -export interface RealtimeData { - countries: Record; - events: any[]; - pageviews: any[]; - referrers: Record; - timestamp: number; - series: { - views: any[]; - visitors: any[]; - }; - totals: { - views: number; - visitors: number; - events: number; - countries: number; - }; - urls: Record; - visitors: any[]; -} +import { RealtimeData } from '@/lib/types'; export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); diff --git a/src/lib/types.ts b/src/lib/types.ts index 1237f519..e5d4ecc5 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -116,3 +116,23 @@ export interface PageResult { sortDescending?: boolean; search?: string; } + +export interface RealtimeData { + countries: Record; + events: any[]; + pageviews: any[]; + referrers: Record; + timestamp: number; + series: { + views: any[]; + visitors: any[]; + }; + totals: { + views: number; + visitors: number; + events: number; + countries: number; + }; + urls: Record; + visitors: any[]; +}