Merge remote-tracking branch 'origin/dev' into dev
Some checks are pending
Node.js CI / build (postgresql, 18.18, 10) (push) Waiting to run

This commit is contained in:
Mike Cao 2025-11-12 16:40:50 -08:00
commit 97ebdc1bab
8 changed files with 67 additions and 70 deletions

View file

@ -1,21 +1,15 @@
import { REALTIME_RANGE } from '@/lib/constants'; import { REALTIME_RANGE } from '@/lib/constants';
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 { timezoneParam } from '@/lib/schema';
import { canViewWebsite } from '@/permissions'; import { canViewWebsite } from '@/permissions';
import { getRealtimeData } from '@/queries/sql'; import { getRealtimeData } from '@/queries/sql';
import { startOfMinute, subMinutes } from 'date-fns'; import { startOfMinute, subMinutes } from 'date-fns';
import z from 'zod';
export async function GET( export async function GET(
request: Request, request: Request,
{ params }: { params: Promise<{ websiteId: string }> }, { params }: { params: Promise<{ websiteId: string }> },
) { ) {
const schema = z.object({ const { auth, query, error } = await parseRequest(request);
timezone: timezoneParam,
});
const { auth, query, error } = await parseRequest(request, schema);
if (error) { if (error) {
return error(); return error();

View file

@ -146,6 +146,7 @@ export async function POST(request: Request) {
region, region,
city, city,
distinctId: id, distinctId: id,
createdAt,
}); });
} }

View file

@ -1,34 +1,13 @@
import { useTimezone } from '@/components/hooks/useTimezone';
import { REALTIME_INTERVAL } from '@/lib/constants'; import { REALTIME_INTERVAL } from '@/lib/constants';
import { useApi } from '../useApi'; import { useApi } from '../useApi';
import { RealtimeData } from '@/lib/types';
export interface RealtimeData {
countries: Record<string, number>;
events: any[];
pageviews: any[];
referrers: Record<string, number>;
timestamp: number;
series: {
views: any[];
visitors: any[];
};
totals: {
views: number;
visitors: number;
events: number;
countries: number;
};
urls: Record<string, number>;
visitors: any[];
}
export function useRealtimeQuery(websiteId: string) { export function useRealtimeQuery(websiteId: string) {
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
const { timezone } = useTimezone();
const { data, isLoading, error } = useQuery<RealtimeData>({ const { data, isLoading, error } = useQuery<RealtimeData>({
queryKey: ['realtime', { websiteId, timezone }], queryKey: ['realtime', { websiteId }],
queryFn: async () => { queryFn: async () => {
return get(`/realtime/${websiteId}`, { timezone }); return get(`/realtime/${websiteId}`);
}, },
enabled: !!websiteId, enabled: !!websiteId,
refetchInterval: REALTIME_INTERVAL, refetchInterval: REALTIME_INTERVAL,

View file

@ -61,7 +61,7 @@ function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) {
function getDateSQL(field: string, unit: string, timezone?: string) { function getDateSQL(field: string, unit: string, timezone?: string) {
if (timezone) { if (timezone) {
return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'), '${timezone}')`; return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'))`;
} }
return `toDateTime(date_trunc('${unit}', ${field}))`; return `toDateTime(date_trunc('${unit}', ${field}))`;
} }

View file

@ -116,3 +116,23 @@ export interface PageResult<T> {
sortDescending?: boolean; sortDescending?: boolean;
search?: string; search?: string;
} }
export interface RealtimeData {
countries: Record<string, number>;
events: any[];
pageviews: any[];
referrers: Record<string, number>;
timestamp: number;
series: {
views: any[];
visitors: any[];
};
totals: {
views: number;
visitors: number;
events: number;
countries: number;
};
urls: Record<string, number>;
visitors: any[];
}

View file

@ -45,7 +45,7 @@ async function clickhouseQuery(
websiteId: string, websiteId: string,
filters: QueryFilters, filters: QueryFilters,
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'UTC', unit = 'day' } = filters;
const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { parseFilters, rawQuery, getDateSQL } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ const { filterQuery, cohortQuery, queryParams } = parseFilters({
...filters, ...filters,

View file

@ -1,41 +1,44 @@
import { Prisma } from '@/generated/prisma/client'; import { Prisma } from '@/generated/prisma/client';
import prisma from '@/lib/prisma'; import prisma from '@/lib/prisma';
export async function createSession(data: Prisma.SessionCreateInput) { const FUNCTION_NAME = 'createSession';
const {
id,
websiteId,
browser,
os,
device,
screen,
language,
country,
region,
city,
distinctId,
} = data;
try { export async function createSession(data: Prisma.SessionCreateInput) {
return await prisma.client.session.create({ const { rawQuery } = prisma;
data: {
id, await rawQuery(
websiteId, `
browser, insert into session (
os, session_id,
device, website_id,
screen, browser,
language, os,
country, device,
region, screen,
city, language,
distinctId, country,
}, region,
}); city,
} catch (e: any) { distinct_id,
if (e.message.toLowerCase().includes('unique constraint')) { created_at
return null; )
} values (
throw e; {{id}},
} {{websiteId}},
{{browser}},
{{os}},
{{device}},
{{screen}},
{{language}},
{{country}},
{{region}},
{{city}},
{{distinctId}},
{{createdAt}}
)
on conflict (session_id) do nothing
`,
data,
FUNCTION_NAME,
);
} }

View file

@ -45,7 +45,7 @@ async function clickhouseQuery(
websiteId: string, websiteId: string,
filters: QueryFilters, filters: QueryFilters,
): Promise<{ x: string; y: number }[]> { ): Promise<{ x: string; y: number }[]> {
const { timezone = 'utc', unit = 'day' } = filters; const { timezone = 'UTC', unit = 'day' } = filters;
const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { parseFilters, rawQuery, getDateSQL } = clickhouse;
const { filterQuery, cohortQuery, queryParams } = parseFilters({ const { filterQuery, cohortQuery, queryParams } = parseFilters({
...filters, ...filters,