mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
app and db schema - region rename, hostname move
This commit is contained in:
parent
5dccca0c3f
commit
12b8ac4272
19 changed files with 11252 additions and 89 deletions
122
db/clickhouse/migrations/06_update_subdivision.sql
Normal file
122
db/clickhouse/migrations/06_update_subdivision.sql
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
-- drop projections
|
||||||
|
ALTER TABLE umami.website_event DROP PROJECTION website_event_url_path_projection;
|
||||||
|
ALTER TABLE umami.website_event DROP PROJECTION website_event_referrer_domain_projection;
|
||||||
|
|
||||||
|
--drop view
|
||||||
|
DROP TABLE umami.website_event_stats_hourly_mv;
|
||||||
|
|
||||||
|
-- rename columns
|
||||||
|
ALTER TABLE umami.website_event RENAME COLUMN "subdivision1" TO "region";
|
||||||
|
ALTER TABLE umami.website_event_stats_hourly RENAME COLUMN "subdivision1" TO "region";
|
||||||
|
|
||||||
|
-- drop columns
|
||||||
|
ALTER TABLE umami.website_event DROP COLUMN "subdivision2";
|
||||||
|
|
||||||
|
-- recreate projections
|
||||||
|
ALTER TABLE umami.website_event
|
||||||
|
ADD PROJECTION website_event_url_path_projection (
|
||||||
|
SELECT * ORDER BY toStartOfDay(created_at), website_id, url_path, created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE umami.website_event MATERIALIZE PROJECTION website_event_url_path_projection;
|
||||||
|
|
||||||
|
ALTER TABLE umami.website_event
|
||||||
|
ADD PROJECTION website_event_referrer_domain_projection (
|
||||||
|
SELECT * ORDER BY toStartOfDay(created_at), website_id, referrer_domain, created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE umami.website_event MATERIALIZE PROJECTION website_event_referrer_domain_projection;
|
||||||
|
|
||||||
|
-- recreate view
|
||||||
|
CREATE MATERIALIZED VIEW umami.website_event_stats_hourly_mv
|
||||||
|
TO umami.website_event_stats_hourly
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
website_id,
|
||||||
|
session_id,
|
||||||
|
visit_id,
|
||||||
|
hostname,
|
||||||
|
browser,
|
||||||
|
os,
|
||||||
|
device,
|
||||||
|
screen,
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
city,
|
||||||
|
entry_url,
|
||||||
|
exit_url,
|
||||||
|
url_paths as url_path,
|
||||||
|
url_query,
|
||||||
|
utm_source,
|
||||||
|
utm_medium,
|
||||||
|
utm_campaign,
|
||||||
|
utm_content,
|
||||||
|
utm_term,
|
||||||
|
referrer_domain,
|
||||||
|
page_title,
|
||||||
|
gclid,
|
||||||
|
fbclid,
|
||||||
|
msclkid,
|
||||||
|
ttclid,
|
||||||
|
li_fat_id,
|
||||||
|
twclid,
|
||||||
|
event_type,
|
||||||
|
event_name,
|
||||||
|
views,
|
||||||
|
min_time,
|
||||||
|
max_time,
|
||||||
|
tag,
|
||||||
|
timestamp as created_at
|
||||||
|
FROM (SELECT
|
||||||
|
website_id,
|
||||||
|
session_id,
|
||||||
|
visit_id,
|
||||||
|
hostname,
|
||||||
|
browser,
|
||||||
|
os,
|
||||||
|
device,
|
||||||
|
screen,
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
city,
|
||||||
|
argMinState(url_path, created_at) entry_url,
|
||||||
|
argMaxState(url_path, created_at) exit_url,
|
||||||
|
arrayFilter(x -> x != '', groupArray(url_path)) as url_paths,
|
||||||
|
arrayFilter(x -> x != '', groupArray(url_query)) url_query,
|
||||||
|
arrayFilter(x -> x != '', groupArray(utm_source)) utm_source,
|
||||||
|
arrayFilter(x -> x != '', groupArray(utm_medium)) utm_medium,
|
||||||
|
arrayFilter(x -> x != '', groupArray(utm_campaign)) utm_campaign,
|
||||||
|
arrayFilter(x -> x != '', groupArray(utm_content)) utm_content,
|
||||||
|
arrayFilter(x -> x != '', groupArray(utm_term)) utm_term,
|
||||||
|
arrayFilter(x -> x != '', groupArray(referrer_domain)) referrer_domain,
|
||||||
|
arrayFilter(x -> x != '', groupArray(page_title)) page_title,
|
||||||
|
arrayFilter(x -> x != '', groupArray(gclid)) gclid,
|
||||||
|
arrayFilter(x -> x != '', groupArray(fbclid)) fbclid,
|
||||||
|
arrayFilter(x -> x != '', groupArray(msclkid)) msclkid,
|
||||||
|
arrayFilter(x -> x != '', groupArray(ttclid)) ttclid,
|
||||||
|
arrayFilter(x -> x != '', groupArray(li_fat_id)) li_fat_id,
|
||||||
|
arrayFilter(x -> x != '', groupArray(twclid)) twclid,
|
||||||
|
event_type,
|
||||||
|
if(event_type = 2, groupArray(event_name), []) event_name,
|
||||||
|
sumIf(1, event_type = 1) views,
|
||||||
|
min(created_at) min_time,
|
||||||
|
max(created_at) max_time,
|
||||||
|
arrayFilter(x -> x != '', groupArray(tag)) tag,
|
||||||
|
toStartOfHour(created_at) timestamp
|
||||||
|
FROM umami.website_event
|
||||||
|
GROUP BY website_id,
|
||||||
|
session_id,
|
||||||
|
visit_id,
|
||||||
|
hostname,
|
||||||
|
browser,
|
||||||
|
os,
|
||||||
|
device,
|
||||||
|
screen,
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
city,
|
||||||
|
event_type,
|
||||||
|
timestamp);
|
||||||
|
|
@ -13,8 +13,7 @@ CREATE TABLE umami.website_event
|
||||||
screen LowCardinality(String),
|
screen LowCardinality(String),
|
||||||
language LowCardinality(String),
|
language LowCardinality(String),
|
||||||
country LowCardinality(String),
|
country LowCardinality(String),
|
||||||
subdivision1 LowCardinality(String),
|
region LowCardinality(String),
|
||||||
subdivision2 LowCardinality(String),
|
|
||||||
city String,
|
city String,
|
||||||
--pageviews
|
--pageviews
|
||||||
url_path String,
|
url_path String,
|
||||||
|
|
@ -96,7 +95,7 @@ CREATE TABLE umami.website_event_stats_hourly
|
||||||
screen LowCardinality(String),
|
screen LowCardinality(String),
|
||||||
language LowCardinality(String),
|
language LowCardinality(String),
|
||||||
country LowCardinality(String),
|
country LowCardinality(String),
|
||||||
subdivision1 LowCardinality(String),
|
region LowCardinality(String),
|
||||||
city String,
|
city String,
|
||||||
entry_url AggregateFunction(argMin, String, DateTime('UTC')),
|
entry_url AggregateFunction(argMin, String, DateTime('UTC')),
|
||||||
exit_url AggregateFunction(argMax, String, DateTime('UTC')),
|
exit_url AggregateFunction(argMax, String, DateTime('UTC')),
|
||||||
|
|
@ -148,7 +147,7 @@ SELECT
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
entry_url,
|
entry_url,
|
||||||
exit_url,
|
exit_url,
|
||||||
|
|
@ -185,7 +184,7 @@ FROM (SELECT
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
argMinState(url_path, created_at) entry_url,
|
argMinState(url_path, created_at) entry_url,
|
||||||
argMaxState(url_path, created_at) exit_url,
|
argMaxState(url_path, created_at) exit_url,
|
||||||
|
|
@ -222,7 +221,7 @@ GROUP BY website_id,
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
event_type,
|
event_type,
|
||||||
timestamp);
|
timestamp);
|
||||||
|
|
|
||||||
22
db/mysql/migrations/09_update_hostname_region/migration.sql
Normal file
22
db/mysql/migrations/09_update_hostname_region/migration.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `website_event` ADD COLUMN `hostname` VARCHAR(100) NULL;
|
||||||
|
|
||||||
|
-- DataMigration
|
||||||
|
UPDATE `website_event` w
|
||||||
|
JOIN `session` s
|
||||||
|
ON s.website_id = w.website_id
|
||||||
|
and s.session_id = w.session_id
|
||||||
|
SET w.hostname = s.hostname;
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX `session_website_id_created_at_hostname_idx` ON `session`;
|
||||||
|
DROP INDEX `session_website_id_created_at_subdivision1_idx` ON `session`;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `session` RENAME COLUMN `subdivision1` TO `region`;
|
||||||
|
ALTER TABLE `session` DROP COLUMN `subdivision2`;
|
||||||
|
ALTER TABLE `session` DROP COLUMN `hostname`;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX `website_event_website_id_created_at_hostname_idx` ON `website_event`(`website_id`, `created_at`, `hostname`);
|
||||||
|
CREATE INDEX `session_website_id_created_at_region_idx` ON `session`(`website_id`, `created_at`, `region`);
|
||||||
|
|
@ -31,15 +31,13 @@ model User {
|
||||||
model Session {
|
model Session {
|
||||||
id String @id @unique @map("session_id") @db.VarChar(36)
|
id String @id @unique @map("session_id") @db.VarChar(36)
|
||||||
websiteId String @map("website_id") @db.VarChar(36)
|
websiteId String @map("website_id") @db.VarChar(36)
|
||||||
hostname String? @db.VarChar(100)
|
|
||||||
browser String? @db.VarChar(20)
|
browser String? @db.VarChar(20)
|
||||||
os String? @db.VarChar(20)
|
os String? @db.VarChar(20)
|
||||||
device String? @db.VarChar(20)
|
device String? @db.VarChar(20)
|
||||||
screen String? @db.VarChar(11)
|
screen String? @db.VarChar(11)
|
||||||
language String? @db.VarChar(35)
|
language String? @db.VarChar(35)
|
||||||
country String? @db.Char(2)
|
country String? @db.Char(2)
|
||||||
subdivision1 String? @db.Char(20)
|
region String? @db.Char(20)
|
||||||
subdivision2 String? @db.VarChar(50)
|
|
||||||
city String? @db.VarChar(50)
|
city String? @db.VarChar(50)
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||||
|
|
||||||
|
|
@ -49,14 +47,13 @@ model Session {
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([websiteId])
|
@@index([websiteId])
|
||||||
@@index([websiteId, createdAt])
|
@@index([websiteId, createdAt])
|
||||||
@@index([websiteId, createdAt, hostname])
|
|
||||||
@@index([websiteId, createdAt, browser])
|
@@index([websiteId, createdAt, browser])
|
||||||
@@index([websiteId, createdAt, os])
|
@@index([websiteId, createdAt, os])
|
||||||
@@index([websiteId, createdAt, device])
|
@@index([websiteId, createdAt, device])
|
||||||
@@index([websiteId, createdAt, screen])
|
@@index([websiteId, createdAt, screen])
|
||||||
@@index([websiteId, createdAt, language])
|
@@index([websiteId, createdAt, language])
|
||||||
@@index([websiteId, createdAt, country])
|
@@index([websiteId, createdAt, country])
|
||||||
@@index([websiteId, createdAt, subdivision1])
|
@@index([websiteId, createdAt, region])
|
||||||
@@index([websiteId, createdAt, city])
|
@@index([websiteId, createdAt, city])
|
||||||
@@map("session")
|
@@map("session")
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +112,7 @@ model WebsiteEvent {
|
||||||
eventType Int @default(1) @map("event_type") @db.UnsignedInt
|
eventType Int @default(1) @map("event_type") @db.UnsignedInt
|
||||||
eventName String? @map("event_name") @db.VarChar(50)
|
eventName String? @map("event_name") @db.VarChar(50)
|
||||||
tag String? @db.VarChar(50)
|
tag String? @db.VarChar(50)
|
||||||
|
hostname String? @db.VarChar(100)
|
||||||
|
|
||||||
eventData EventData[]
|
eventData EventData[]
|
||||||
session Session @relation(fields: [sessionId], references: [id])
|
session Session @relation(fields: [sessionId], references: [id])
|
||||||
|
|
@ -132,6 +130,7 @@ model WebsiteEvent {
|
||||||
@@index([websiteId, createdAt, tag])
|
@@index([websiteId, createdAt, tag])
|
||||||
@@index([websiteId, sessionId, createdAt])
|
@@index([websiteId, sessionId, createdAt])
|
||||||
@@index([websiteId, visitId, createdAt])
|
@@index([websiteId, visitId, createdAt])
|
||||||
|
@@index([websiteId, createdAt, hostname])
|
||||||
@@map("website_event")
|
@@map("website_event")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "website_event" ADD COLUMN "hostname" VARCHAR(100);
|
||||||
|
|
||||||
|
-- DataMigration
|
||||||
|
UPDATE "website_event" w
|
||||||
|
SET hostname = s.hostname
|
||||||
|
FROM "session" s
|
||||||
|
WHERE s.website_id = w.website_id
|
||||||
|
and s.session_id = w.session_id;
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX IF EXISTS "session_website_id_created_at_hostname_idx";
|
||||||
|
DROP INDEX IF EXISTS "session_website_id_created_at_subdivision1_idx";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "session" RENAME COLUMN "subdivision1" TO "region";
|
||||||
|
ALTER TABLE "session" DROP COLUMN "subdivision2";
|
||||||
|
ALTER TABLE "session" DROP COLUMN "hostname";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "website_event_website_id_created_at_hostname_idx" ON "website_event"("website_id", "created_at", "hostname");
|
||||||
|
CREATE INDEX "session_website_id_created_at_region_idx" ON "session"("website_id", "created_at", "region");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,15 +31,13 @@ model User {
|
||||||
model Session {
|
model Session {
|
||||||
id String @id @unique @map("session_id") @db.Uuid
|
id String @id @unique @map("session_id") @db.Uuid
|
||||||
websiteId String @map("website_id") @db.Uuid
|
websiteId String @map("website_id") @db.Uuid
|
||||||
hostname String? @db.VarChar(100)
|
|
||||||
browser String? @db.VarChar(20)
|
browser String? @db.VarChar(20)
|
||||||
os String? @db.VarChar(20)
|
os String? @db.VarChar(20)
|
||||||
device String? @db.VarChar(20)
|
device String? @db.VarChar(20)
|
||||||
screen String? @db.VarChar(11)
|
screen String? @db.VarChar(11)
|
||||||
language String? @db.VarChar(35)
|
language String? @db.VarChar(35)
|
||||||
country String? @db.Char(2)
|
country String? @db.Char(2)
|
||||||
subdivision1 String? @db.VarChar(20)
|
region String? @db.VarChar(20)
|
||||||
subdivision2 String? @db.VarChar(50)
|
|
||||||
city String? @db.VarChar(50)
|
city String? @db.VarChar(50)
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
|
||||||
|
|
@ -49,14 +47,13 @@ model Session {
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([websiteId])
|
@@index([websiteId])
|
||||||
@@index([websiteId, createdAt])
|
@@index([websiteId, createdAt])
|
||||||
@@index([websiteId, createdAt, hostname])
|
|
||||||
@@index([websiteId, createdAt, browser])
|
@@index([websiteId, createdAt, browser])
|
||||||
@@index([websiteId, createdAt, os])
|
@@index([websiteId, createdAt, os])
|
||||||
@@index([websiteId, createdAt, device])
|
@@index([websiteId, createdAt, device])
|
||||||
@@index([websiteId, createdAt, screen])
|
@@index([websiteId, createdAt, screen])
|
||||||
@@index([websiteId, createdAt, language])
|
@@index([websiteId, createdAt, language])
|
||||||
@@index([websiteId, createdAt, country])
|
@@index([websiteId, createdAt, country])
|
||||||
@@index([websiteId, createdAt, subdivision1])
|
@@index([websiteId, createdAt, region])
|
||||||
@@index([websiteId, createdAt, city])
|
@@index([websiteId, createdAt, city])
|
||||||
@@map("session")
|
@@map("session")
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +112,7 @@ model WebsiteEvent {
|
||||||
eventType Int @default(1) @map("event_type") @db.Integer
|
eventType Int @default(1) @map("event_type") @db.Integer
|
||||||
eventName String? @map("event_name") @db.VarChar(50)
|
eventName String? @map("event_name") @db.VarChar(50)
|
||||||
tag String? @db.VarChar(50)
|
tag String? @db.VarChar(50)
|
||||||
|
hostname String? @db.VarChar(100)
|
||||||
|
|
||||||
eventData EventData[]
|
eventData EventData[]
|
||||||
session Session @relation(fields: [sessionId], references: [id])
|
session Session @relation(fields: [sessionId], references: [id])
|
||||||
|
|
@ -132,6 +130,7 @@ model WebsiteEvent {
|
||||||
@@index([websiteId, createdAt, tag])
|
@@index([websiteId, createdAt, tag])
|
||||||
@@index([websiteId, sessionId, createdAt])
|
@@index([websiteId, sessionId, createdAt])
|
||||||
@@index([websiteId, visitId, createdAt])
|
@@index([websiteId, visitId, createdAt])
|
||||||
|
@@index([websiteId, createdAt, hostname])
|
||||||
@@map("website_event")
|
@@map("website_event")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default function SessionInfo({ data }) {
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Location />
|
<Icons.Location />
|
||||||
</Icon>
|
</Icon>
|
||||||
{getRegionName(data?.subdivision1)}
|
{getRegionName(data?.region)}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt>{formatMessage(labels.city)}</dt>
|
<dt>{formatMessage(labels.city)}</dt>
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,10 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client info
|
// Client info
|
||||||
const { ip, userAgent, device, browser, os, country, subdivision1, subdivision2, city } =
|
const { ip, userAgent, device, browser, os, country, region, city } = await getClientInfo(
|
||||||
await getClientInfo(request, payload);
|
request,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
|
||||||
// Bot check
|
// Bot check
|
||||||
if (!process.env.DISABLE_BOT_CHECK && isbot(userAgent)) {
|
if (!process.env.DISABLE_BOT_CHECK && isbot(userAgent)) {
|
||||||
|
|
@ -109,15 +111,13 @@ export async function POST(request: Request) {
|
||||||
await createSession({
|
await createSession({
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
websiteId,
|
websiteId,
|
||||||
hostname,
|
|
||||||
browser,
|
browser,
|
||||||
os,
|
os,
|
||||||
device,
|
device,
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
subdivision2,
|
|
||||||
city,
|
city,
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
@ -210,8 +210,7 @@ export async function POST(request: Request) {
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
subdivision2,
|
|
||||||
city,
|
city,
|
||||||
tag,
|
tag,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,17 @@ export const FILTER_REFERRERS = 'filter-referrers';
|
||||||
export const FILTER_PAGES = 'filter-pages';
|
export const FILTER_PAGES = 'filter-pages';
|
||||||
|
|
||||||
export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute'];
|
export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute'];
|
||||||
export const EVENT_COLUMNS = ['url', 'entry', 'exit', 'referrer', 'title', 'query', 'event', 'tag'];
|
export const EVENT_COLUMNS = [
|
||||||
|
'url',
|
||||||
|
'entry',
|
||||||
|
'exit',
|
||||||
|
'referrer',
|
||||||
|
'title',
|
||||||
|
'query',
|
||||||
|
'event',
|
||||||
|
'tag',
|
||||||
|
'region',
|
||||||
|
];
|
||||||
|
|
||||||
export const SESSION_COLUMNS = [
|
export const SESSION_COLUMNS = [
|
||||||
'browser',
|
'browser',
|
||||||
|
|
@ -42,7 +52,6 @@ export const SESSION_COLUMNS = [
|
||||||
'screen',
|
'screen',
|
||||||
'language',
|
'language',
|
||||||
'country',
|
'country',
|
||||||
'region',
|
|
||||||
'city',
|
'city',
|
||||||
'host',
|
'host',
|
||||||
];
|
];
|
||||||
|
|
@ -59,7 +68,7 @@ export const FILTER_COLUMNS = {
|
||||||
browser: 'browser',
|
browser: 'browser',
|
||||||
device: 'device',
|
device: 'device',
|
||||||
country: 'country',
|
country: 'country',
|
||||||
region: 'subdivision1',
|
region: 'region',
|
||||||
city: 'city',
|
city: 'city',
|
||||||
language: 'language',
|
language: 'language',
|
||||||
event: 'event_name',
|
event: 'event_name',
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,12 @@ export async function getLocation(ip: string = '', headers: Headers, hasPayloadI
|
||||||
// Cloudflare headers
|
// Cloudflare headers
|
||||||
if (headers.get('cf-ipcountry')) {
|
if (headers.get('cf-ipcountry')) {
|
||||||
const country = decodeHeader(headers.get('cf-ipcountry'));
|
const country = decodeHeader(headers.get('cf-ipcountry'));
|
||||||
const subdivision1 = decodeHeader(headers.get('cf-region-code'));
|
const region = decodeHeader(headers.get('cf-region-code'));
|
||||||
const city = decodeHeader(headers.get('cf-ipcity'));
|
const city = decodeHeader(headers.get('cf-ipcity'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
country,
|
country,
|
||||||
subdivision1: getRegionCode(country, subdivision1),
|
region: getRegionCode(country, region),
|
||||||
city,
|
city,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -109,12 +109,12 @@ export async function getLocation(ip: string = '', headers: Headers, hasPayloadI
|
||||||
// Vercel headers
|
// Vercel headers
|
||||||
if (headers.get('x-vercel-ip-country')) {
|
if (headers.get('x-vercel-ip-country')) {
|
||||||
const country = decodeHeader(headers.get('x-vercel-ip-country'));
|
const country = decodeHeader(headers.get('x-vercel-ip-country'));
|
||||||
const subdivision1 = decodeHeader(headers.get('x-vercel-ip-country-region'));
|
const region = decodeHeader(headers.get('x-vercel-ip-country-region'));
|
||||||
const city = decodeHeader(headers.get('x-vercel-ip-city'));
|
const city = decodeHeader(headers.get('x-vercel-ip-city'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
country,
|
country,
|
||||||
subdivision1: getRegionCode(country, subdivision1),
|
region: getRegionCode(country, region),
|
||||||
city,
|
city,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -131,14 +131,12 @@ export async function getLocation(ip: string = '', headers: Headers, hasPayloadI
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const country = result.country?.iso_code ?? result?.registered_country?.iso_code;
|
const country = result.country?.iso_code ?? result?.registered_country?.iso_code;
|
||||||
const subdivision1 = result.subdivisions?.[0]?.iso_code;
|
const region = result.subdivisions?.[0]?.iso_code;
|
||||||
const subdivision2 = result.subdivisions?.[1]?.names?.en;
|
|
||||||
const city = result.city?.names?.en;
|
const city = result.city?.names?.en;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
country,
|
country,
|
||||||
subdivision1: getRegionCode(country, subdivision1),
|
region: getRegionCode(country, region),
|
||||||
subdivision2,
|
|
||||||
city,
|
city,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -149,14 +147,13 @@ export async function getClientInfo(request: Request, payload: Record<string, an
|
||||||
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 = location?.country;
|
||||||
const subdivision1 = location?.subdivision1;
|
const region = location?.region;
|
||||||
const subdivision2 = location?.subdivision2;
|
|
||||||
const city = location?.city;
|
const city = 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);
|
||||||
|
|
||||||
return { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device };
|
return { userAgent, browser, os, ip, country, region, city, device };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasBlockedIp(clientIp: string) {
|
export function hasBlockedIp(clientIp: string) {
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}):
|
||||||
|
|
||||||
if (name === 'referrer') {
|
if (name === 'referrer') {
|
||||||
arr.push(
|
arr.push(
|
||||||
`and (website_event.referrer_domain != session.hostname or website_event.referrer_domain is null)`,
|
`and (website_event.referrer_domain != website_event.hostname or website_event.referrer_domain is null)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,8 +197,7 @@ export interface SessionData {
|
||||||
screen: string;
|
screen: string;
|
||||||
language: string;
|
language: string;
|
||||||
country: string;
|
country: string;
|
||||||
subdivision1: string;
|
region: string;
|
||||||
subdivision2: string;
|
|
||||||
city: string;
|
city: string;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,7 @@ export async function saveEvent(args: {
|
||||||
screen?: string;
|
screen?: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
country?: string;
|
country?: string;
|
||||||
subdivision1?: string;
|
region?: string;
|
||||||
subdivision2?: string;
|
|
||||||
city?: string;
|
city?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
|
|
@ -72,6 +71,7 @@ async function relationalQuery(data: {
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventData?: any;
|
eventData?: any;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
hostname?: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
|
|
@ -98,6 +98,7 @@ async function relationalQuery(data: {
|
||||||
lifatid,
|
lifatid,
|
||||||
twclid,
|
twclid,
|
||||||
tag,
|
tag,
|
||||||
|
hostname,
|
||||||
createdAt,
|
createdAt,
|
||||||
} = data;
|
} = data;
|
||||||
const websiteEventId = uuid();
|
const websiteEventId = uuid();
|
||||||
|
|
@ -128,6 +129,7 @@ async function relationalQuery(data: {
|
||||||
eventType: eventName ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
eventType: eventName ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView,
|
||||||
eventName: eventName ? eventName?.substring(0, EVENT_NAME_LENGTH) : null,
|
eventName: eventName ? eventName?.substring(0, EVENT_NAME_LENGTH) : null,
|
||||||
tag,
|
tag,
|
||||||
|
hostname,
|
||||||
createdAt,
|
createdAt,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -177,8 +179,7 @@ async function clickhouseQuery(data: {
|
||||||
screen?: string;
|
screen?: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
country?: string;
|
country?: string;
|
||||||
subdivision1?: string;
|
region?: string;
|
||||||
subdivision2?: string;
|
|
||||||
city?: string;
|
city?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
|
|
@ -207,8 +208,7 @@ async function clickhouseQuery(data: {
|
||||||
eventName,
|
eventName,
|
||||||
eventData,
|
eventData,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
subdivision2,
|
|
||||||
city,
|
city,
|
||||||
tag,
|
tag,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
|
@ -225,13 +225,7 @@ async function clickhouseQuery(data: {
|
||||||
visit_id: visitId,
|
visit_id: visitId,
|
||||||
event_id: eventId,
|
event_id: eventId,
|
||||||
country: country,
|
country: country,
|
||||||
subdivision1:
|
region: country && region ? (region.includes('-') ? region : `${country}-${region}`) : null,
|
||||||
country && subdivision1
|
|
||||||
? subdivision1.includes('-')
|
|
||||||
? subdivision1
|
|
||||||
: `${country}-${subdivision1}`
|
|
||||||
: null,
|
|
||||||
subdivision2: subdivision2,
|
|
||||||
city: city,
|
city: city,
|
||||||
url_path: urlPath?.substring(0, URL_LENGTH),
|
url_path: urlPath?.substring(0, URL_LENGTH),
|
||||||
url_query: urlQuery?.substring(0, URL_LENGTH),
|
url_query: urlQuery?.substring(0, URL_LENGTH),
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ async function relationalQuery(
|
||||||
let excludeDomain = '';
|
let excludeDomain = '';
|
||||||
|
|
||||||
if (column === 'referrer_domain') {
|
if (column === 'referrer_domain') {
|
||||||
excludeDomain = `and website_event.referrer_domain != session.hostname
|
excludeDomain = `and website_event.referrer_domain != website_event.hostname
|
||||||
and website_event.referrer_domain != ''`;
|
and website_event.referrer_domain != ''`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,34 +2,19 @@ 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) {
|
||||||
const {
|
const { id, websiteId, browser, os, device, screen, language, country, region, city } = data;
|
||||||
id,
|
|
||||||
websiteId,
|
|
||||||
hostname,
|
|
||||||
browser,
|
|
||||||
os,
|
|
||||||
device,
|
|
||||||
screen,
|
|
||||||
language,
|
|
||||||
country,
|
|
||||||
subdivision1,
|
|
||||||
subdivision2,
|
|
||||||
city,
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
return prisma.client.session.create({
|
return prisma.client.session.create({
|
||||||
data: {
|
data: {
|
||||||
id,
|
id,
|
||||||
websiteId,
|
websiteId,
|
||||||
hostname,
|
|
||||||
browser,
|
browser,
|
||||||
os,
|
os,
|
||||||
device,
|
device,
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
subdivision2,
|
|
||||||
city,
|
city,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ async function relationalQuery(
|
||||||
joinSession: SESSION_COLUMNS.includes(type),
|
joinSession: SESSION_COLUMNS.includes(type),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const includeCountry = column === 'city' || column === 'subdivision1';
|
const includeCountry = column === 'city' || column === 'region';
|
||||||
|
|
||||||
return rawQuery(
|
return rawQuery(
|
||||||
`
|
`
|
||||||
|
|
@ -75,7 +75,7 @@ async function clickhouseQuery(
|
||||||
...filters,
|
...filters,
|
||||||
eventType: EVENT_TYPE.pageView,
|
eventType: EVENT_TYPE.pageView,
|
||||||
});
|
});
|
||||||
const includeCountry = column === 'city' || column === 'subdivision1';
|
const includeCountry = column === 'city' || column === 'region';
|
||||||
|
|
||||||
let sql = '';
|
let sql = '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ async function relationalQuery(websiteId: string, sessionId: string) {
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
min(min_time) as "firstAt",
|
min(min_time) as "firstAt",
|
||||||
max(max_time) as "lastAt",
|
max(max_time) as "lastAt",
|
||||||
|
|
@ -35,14 +35,14 @@ async function relationalQuery(websiteId: string, sessionId: string) {
|
||||||
session.session_id as id,
|
session.session_id as id,
|
||||||
website_event.visit_id,
|
website_event.visit_id,
|
||||||
session.website_id,
|
session.website_id,
|
||||||
session.hostname,
|
website_event.hostname,
|
||||||
session.browser,
|
session.browser,
|
||||||
session.os,
|
session.os,
|
||||||
session.device,
|
session.device,
|
||||||
session.screen,
|
session.screen,
|
||||||
session.language,
|
session.language,
|
||||||
session.country,
|
session.country,
|
||||||
session.subdivision1,
|
session.region,
|
||||||
session.city,
|
session.city,
|
||||||
min(website_event.created_at) as min_time,
|
min(website_event.created_at) as min_time,
|
||||||
max(website_event.created_at) as max_time,
|
max(website_event.created_at) as max_time,
|
||||||
|
|
@ -52,8 +52,8 @@ async function relationalQuery(websiteId: string, sessionId: string) {
|
||||||
join website_event on website_event.session_id = session.session_id
|
join website_event on website_event.session_id = session.session_id
|
||||||
where session.website_id = {{websiteId::uuid}}
|
where session.website_id = {{websiteId::uuid}}
|
||||||
and session.session_id = {{sessionId::uuid}}
|
and session.session_id = {{sessionId::uuid}}
|
||||||
group by session.session_id, visit_id, session.website_id, session.hostname, session.browser, session.os, session.device, session.screen, session.language, session.country, session.subdivision1, session.city) t
|
group by session.session_id, visit_id, session.website_id, website_event.hostname, session.browser, session.os, session.device, session.screen, session.language, session.country, session.region, session.city) t
|
||||||
group by id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, city;
|
group by id, website_id, hostname, browser, os, device, screen, language, country, region, city;
|
||||||
`,
|
`,
|
||||||
{ websiteId, sessionId },
|
{ websiteId, sessionId },
|
||||||
).then(result => result?.[0]);
|
).then(result => result?.[0]);
|
||||||
|
|
@ -73,7 +73,7 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
${getDateStringSQL('min(min_time)')} as firstAt,
|
${getDateStringSQL('min(min_time)')} as firstAt,
|
||||||
${getDateStringSQL('max(max_time)')} as lastAt,
|
${getDateStringSQL('max(max_time)')} as lastAt,
|
||||||
|
|
@ -92,7 +92,7 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
min(min_time) as min_time,
|
min(min_time) as min_time,
|
||||||
max(max_time) as max_time,
|
max(max_time) as max_time,
|
||||||
|
|
@ -101,8 +101,8 @@ async function clickhouseQuery(websiteId: string, sessionId: string) {
|
||||||
from website_event_stats_hourly
|
from website_event_stats_hourly
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and session_id = {sessionId:UUID}
|
and session_id = {sessionId:UUID}
|
||||||
group by session_id, visit_id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, city) t
|
group by session_id, visit_id, website_id, hostname, browser, os, device, screen, language, country, region, city) t
|
||||||
group by id, websiteId, hostname, browser, os, device, screen, language, country, subdivision1, city;
|
group by id, websiteId, hostname, browser, os, device, screen, language, country, region, city;
|
||||||
`,
|
`,
|
||||||
{ websiteId, sessionId },
|
{ websiteId, sessionId },
|
||||||
).then(result => result?.[0]);
|
).then(result => result?.[0]);
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,14 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
|
||||||
select
|
select
|
||||||
session.session_id as "id",
|
session.session_id as "id",
|
||||||
session.website_id as "websiteId",
|
session.website_id as "websiteId",
|
||||||
session.hostname,
|
website_event.hostname,
|
||||||
session.browser,
|
session.browser,
|
||||||
session.os,
|
session.os,
|
||||||
session.device,
|
session.device,
|
||||||
session.screen,
|
session.screen,
|
||||||
session.language,
|
session.language,
|
||||||
session.country,
|
session.country,
|
||||||
session.subdivision1,
|
session.region,
|
||||||
session.city,
|
session.city,
|
||||||
min(website_event.created_at) as "firstAt",
|
min(website_event.created_at) as "firstAt",
|
||||||
max(website_event.created_at) as "lastAt",
|
max(website_event.created_at) as "lastAt",
|
||||||
|
|
@ -45,14 +45,14 @@ async function relationalQuery(websiteId: string, filters: QueryFilters, pagePar
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by session.session_id,
|
group by session.session_id,
|
||||||
session.website_id,
|
session.website_id,
|
||||||
session.hostname,
|
website_event.hostname,
|
||||||
session.browser,
|
session.browser,
|
||||||
session.os,
|
session.os,
|
||||||
session.device,
|
session.device,
|
||||||
session.screen,
|
session.screen,
|
||||||
session.language,
|
session.language,
|
||||||
session.country,
|
session.country,
|
||||||
session.subdivision1,
|
session.region,
|
||||||
session.city
|
session.city
|
||||||
order by max(website_event.created_at) desc
|
order by max(website_event.created_at) desc
|
||||||
limit 1000)
|
limit 1000)
|
||||||
|
|
@ -80,7 +80,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
country,
|
country,
|
||||||
subdivision1,
|
region,
|
||||||
city,
|
city,
|
||||||
${getDateStringSQL('min(min_time)')} as firstAt,
|
${getDateStringSQL('min(min_time)')} as firstAt,
|
||||||
${getDateStringSQL('max(max_time)')} as lastAt,
|
${getDateStringSQL('max(max_time)')} as lastAt,
|
||||||
|
|
@ -91,7 +91,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters, pagePar
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by session_id, website_id, hostname, browser, os, device, screen, language, country, subdivision1, 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)
|
limit 1000)
|
||||||
select * from sessions
|
select * from sessions
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue