tianji/src/server/utils/prisma.ts

148 lines
3.6 KiB
TypeScript
Raw Normal View History

import { Prisma } from '@prisma/client';
import dayjs from 'dayjs';
import _ from 'lodash';
2023-09-12 15:04:39 +00:00
import { loadWebsite } from '../model/website';
import { maxDate } from './common';
import { FILTER_COLUMNS, OPERATORS, SESSION_COLUMNS } from './const';
const POSTGRESQL_DATE_FORMATS = {
minute: 'YYYY-MM-DD HH24:MI:00',
hour: 'YYYY-MM-DD HH24:00:00',
day: 'YYYY-MM-DD',
month: 'YYYY-MM-01',
year: 'YYYY-01-01',
};
export interface QueryFilters {
startDate?: Date;
endDate?: Date;
timezone?: string;
unit?: keyof typeof POSTGRESQL_DATE_FORMATS;
eventType?: number;
url?: string;
referrer?: string;
title?: string;
query?: string;
os?: string;
browser?: string;
device?: string;
country?: string;
region?: string;
city?: string;
language?: string;
event?: string;
}
export interface QueryOptions {
joinSession?: boolean;
columns?: { [key: string]: string };
}
export async function parseFilters(
websiteId: string,
filters: QueryFilters = {},
options: QueryOptions = {}
) {
const website = await loadWebsite(websiteId);
if (!website) {
throw new Error('Not found website');
}
2023-09-16 07:50:36 +00:00
const websiteDomain = website.domain;
2023-09-12 15:04:39 +00:00
return {
joinSession:
options?.joinSession ||
Object.entries(filters).find(
([key, value]) =>
typeof value !== 'undefined' && SESSION_COLUMNS.includes(key)
)
2023-09-16 08:12:19 +00:00
? Prisma.sql([
`inner join "WebsiteSession" on "WebsiteEvent"."sessionId" = "WebsiteSession"."id"`,
])
: Prisma.empty,
2023-09-16 07:50:36 +00:00
filterQuery: getFilterQuery(filters, options, websiteDomain),
2023-09-12 15:04:39 +00:00
params: {
...normalizeFilters(filters),
websiteId,
startDate: dayjs(
maxDate(filters.startDate, website.resetAt)
).toISOString(),
endDate: filters.endDate
? dayjs(filters.endDate).toISOString()
: undefined,
2023-09-16 07:50:36 +00:00
websiteDomain,
2023-09-12 15:04:39 +00:00
},
};
}
function normalizeFilters(filters: Record<string, any> = {}) {
return Object.keys(filters).reduce((obj, key) => {
const value = filters[key];
obj[key] = value?.value ?? value;
return obj;
}, {} as Record<string, any>);
}
export function getFilterQuery(
filters: QueryFilters = {},
2023-09-16 07:50:36 +00:00
options: QueryOptions = {},
websiteDomain: string | null = null
) {
2023-09-12 15:04:39 +00:00
const query = Object.keys(filters).reduce<string[]>((arr, name) => {
const value: any = filters[name as keyof QueryFilters];
const operator = value?.filter ?? OPERATORS.equals;
const column = _.get(FILTER_COLUMNS, name, options?.columns?.[name]);
2023-09-12 15:04:39 +00:00
// TODO
2023-09-12 15:04:39 +00:00
if (value !== undefined && column) {
2023-09-16 07:50:36 +00:00
arr.push(`AND ${mapFilter(column, operator, name)}`);
2023-09-12 15:04:39 +00:00
if (name === 'referrer') {
arr.push(
2023-09-16 07:50:36 +00:00
`AND ("WebsiteEvent"."referrerDomain" != ${websiteDomain} or "WebsiteEvent"."referrerDomain" is null)`
2023-09-12 15:04:39 +00:00
);
}
}
return arr;
}, []);
2023-09-16 07:50:36 +00:00
return Prisma.sql([query.join('\n')]);
2023-09-12 15:04:39 +00:00
}
function mapFilter(
column: string,
operator: (typeof OPERATORS)[keyof typeof OPERATORS],
name: string,
type = 'varchar'
) {
switch (operator) {
case OPERATORS.equals:
2023-09-16 07:50:36 +00:00
return `"${column}" = '${name}'::${type}`;
2023-09-12 15:04:39 +00:00
case OPERATORS.notEquals:
2023-09-16 07:50:36 +00:00
return `"${column}" != '${name}'::${type}`;
2023-09-12 15:04:39 +00:00
default:
return '';
}
}
export function getDateQuery(
field: string,
unit: keyof typeof POSTGRESQL_DATE_FORMATS,
timezone?: string
) {
2023-09-12 15:04:39 +00:00
if (timezone) {
return Prisma.sql([
`to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${POSTGRESQL_DATE_FORMATS[unit]}')`,
]);
2023-09-12 15:04:39 +00:00
}
return Prisma.sql([
`to_char(date_trunc('${unit}', ${field}), '${POSTGRESQL_DATE_FORMATS[unit]}')`,
]);
2023-09-12 15:04:39 +00:00
}