Merge branch 'dev' of https://github.com/umami-software/umami into feat/add-segments

This commit is contained in:
Francis Cao 2025-06-26 09:12:51 -07:00
commit a753809a74
5 changed files with 56 additions and 60 deletions

View file

@ -4,7 +4,7 @@ import { startOfHour, startOfMonth } from 'date-fns';
import clickhouse from '@/lib/clickhouse'; import clickhouse from '@/lib/clickhouse';
import { parseRequest } from '@/lib/request'; import { parseRequest } from '@/lib/request';
import { badRequest, json, forbidden, serverError } from '@/lib/response'; import { badRequest, json, forbidden, serverError } from '@/lib/response';
import { fetchSession, fetchWebsite } from '@/lib/load'; import { fetchWebsite } from '@/lib/load';
import { getClientInfo, hasBlockedIp } from '@/lib/detect'; import { getClientInfo, hasBlockedIp } from '@/lib/detect';
import { createToken, parseToken } from '@/lib/jwt'; import { createToken, parseToken } from '@/lib/jwt';
import { secret, uuid, hash } from '@/lib/crypto'; import { secret, uuid, hash } from '@/lib/crypto';
@ -103,32 +103,24 @@ export async function POST(request: Request) {
const sessionId = id ? uuid(websiteId, id) : uuid(websiteId, ip, userAgent, sessionSalt); const sessionId = id ? uuid(websiteId, id) : uuid(websiteId, ip, userAgent, sessionSalt);
// Find session // Create a session if not found
if (!clickhouse.enabled && !cache?.sessionId) { if (!clickhouse.enabled && !cache?.sessionId) {
const session = await fetchSession(websiteId, sessionId); await createSession(
{
// Create a session if not found id: sessionId,
if (!session) { websiteId,
try { browser,
await createSession({ os,
id: sessionId, device,
websiteId, screen,
browser, language,
os, country,
device, region,
screen, city,
language, distinctId: id,
country, },
region, { skipDuplicates: true },
city, );
distinctId: id,
});
} catch (e: any) {
if (!e.message.toLowerCase().includes('unique constraint')) {
return serverError(e);
}
}
}
} }
// Visit info // Visit info

View file

@ -5,12 +5,13 @@ import ipaddr from 'ipaddr.js';
import maxmind from 'maxmind'; import maxmind from 'maxmind';
import { import {
DESKTOP_OS, DESKTOP_OS,
MOBILE_OS,
DESKTOP_SCREEN_WIDTH, DESKTOP_SCREEN_WIDTH,
LAPTOP_SCREEN_WIDTH,
MOBILE_SCREEN_WIDTH,
IP_ADDRESS_HEADERS, IP_ADDRESS_HEADERS,
LAPTOP_SCREEN_WIDTH,
MOBILE_OS,
MOBILE_SCREEN_WIDTH,
} from './constants'; } from './constants';
import { safeDecodeURIComponent } from '@/lib/url';
const MAXMIND = 'maxmind'; const MAXMIND = 'maxmind';
@ -148,9 +149,9 @@ export async function getClientInfo(request: Request, payload: Record<string, an
const userAgent = payload?.userAgent || request.headers.get('user-agent'); const userAgent = payload?.userAgent || request.headers.get('user-agent');
const ip = payload?.ip || getIpAddress(request.headers); const ip = payload?.ip || getIpAddress(request.headers);
const location = await getLocation(ip, request.headers, !!payload?.ip); const location = await getLocation(ip, request.headers, !!payload?.ip);
const country = location?.country; const country = safeDecodeURIComponent(location?.country);
const region = location?.region; const region = safeDecodeURIComponent(location?.region);
const city = location?.city; const city = safeDecodeURIComponent(location?.city);
const browser = browserName(userAgent); const browser = browserName(userAgent);
const os = detectOS(userAgent) as string; const os = detectOS(userAgent) as string;
const device = getDevice(payload?.screen, os); const device = getDevice(payload?.screen, os);

View file

@ -24,7 +24,6 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
return pagedRawQuery( return pagedRawQuery(
` `
with events as (
select select
event_id as "id", event_id as "id",
website_id as "websiteId", website_id as "websiteId",
@ -49,8 +48,6 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
: '' : ''
} }
order by created_at desc order by created_at desc
limit 1000)
select * from events
`, `,
{ ...params, search: `%${search}%` }, { ...params, search: `%${search}%` },
pageParams, pageParams,
@ -64,7 +61,6 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
return pagedQuery( return pagedQuery(
` `
with events as (
select select
event_id as id, event_id as id,
website_id as websiteId, website_id as websiteId,
@ -89,8 +85,6 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
: '' : ''
} }
order by created_at desc order by created_at desc
limit 1000)
select * from events
`, `,
{ ...params, search }, { ...params, search },
pageParams, pageParams,

View file

@ -1,7 +1,10 @@
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import prisma from '@/lib/prisma'; import prisma from '@/lib/prisma';
export async function createSession(data: Prisma.SessionCreateInput) { export async function createSession(
data: Prisma.SessionCreateInput,
options = { skipDuplicates: false },
) {
const { const {
id, id,
websiteId, websiteId,
@ -16,19 +19,31 @@ export async function createSession(data: Prisma.SessionCreateInput) {
distinctId, distinctId,
} = data; } = data;
return prisma.client.session.create({ try {
data: { return await prisma.client.session.create({
id, data: {
websiteId, id,
browser, websiteId,
os, browser,
device, os,
screen, device,
language, screen,
country, language,
region, country,
city, region,
distinctId, city,
}, distinctId,
}); },
});
} catch (e: any) {
// With skipDuplicates flag: ignore unique constraint error and return null
if (
options.skipDuplicates &&
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === 'P2002'
) {
return null;
}
throw e;
}
} }

View file

@ -24,7 +24,6 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
return pagedRawQuery( return pagedRawQuery(
` `
with sessions as (
select select
session.session_id as "id", session.session_id as "id",
session.website_id as "websiteId", session.website_id as "websiteId",
@ -68,8 +67,6 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
session.region, session.region,
session.city session.city
order by max(website_event.created_at) desc order by max(website_event.created_at) desc
limit 1000)
select * from sessions
`, `,
{ ...params, search: `%${search}%` }, { ...params, search: `%${search}%` },
pageParams, pageParams,
@ -83,7 +80,6 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
return pagedQuery( return pagedQuery(
` `
with sessions as (
select select
session_id as id, session_id as id,
website_id as websiteId, website_id as websiteId,
@ -116,8 +112,6 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
} }
group by session_id, website_id, hostname, browser, os, device, screen, language, country, region, city group by session_id, website_id, hostname, browser, os, device, screen, language, country, region, city
order by lastAt desc order by lastAt desc
limit 1000)
select * from sessions
`, `,
{ ...params, search }, { ...params, search },
pageParams, pageParams,