tianji/src/server/utils/prisma.ts
2023-09-19 19:29:28 +08:00

154 lines
3.7 KiB
TypeScript

import { Prisma } from '@prisma/client';
import dayjs from 'dayjs';
import _ from 'lodash';
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');
}
const websiteDomain = website.domain;
return {
joinSession:
options?.joinSession ||
Object.entries(filters).find(
([key, value]) =>
typeof value !== 'undefined' && SESSION_COLUMNS.includes(key)
)
? Prisma.sql([
`inner join "WebsiteSession" on "WebsiteEvent"."sessionId" = "WebsiteSession"."id"`,
])
: Prisma.empty,
filterQuery: getFilterQuery(filters, options, websiteDomain),
params: {
...normalizeFilters(filters),
websiteId,
startDate: dayjs(
maxDate(filters.startDate, website.resetAt)
).toISOString(),
endDate: filters.endDate
? dayjs(filters.endDate).toISOString()
: undefined,
websiteDomain,
},
};
}
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 = {},
options: QueryOptions = {},
websiteDomain: string | null = null
) {
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]);
// TODO
if (value !== undefined && column) {
arr.push(`AND ${mapFilter(column, operator, name)}`);
if (name === 'referrer') {
arr.push(
`AND ("WebsiteEvent"."referrerDomain" != ${websiteDomain} or "WebsiteEvent"."referrerDomain" is null)`
);
}
}
return arr;
}, []);
return Prisma.sql([query.join('\n')]);
}
function mapFilter(
column: string,
operator: (typeof OPERATORS)[keyof typeof OPERATORS],
name: string,
type = 'varchar'
) {
switch (operator) {
case OPERATORS.equals:
return `"${column}" = '${name}'::${type}`;
case OPERATORS.notEquals:
return `"${column}" != '${name}'::${type}`;
default:
return '';
}
}
export function getDateQuery(
field: string,
unit: keyof typeof POSTGRESQL_DATE_FORMATS,
timezone?: string
) {
if (timezone) {
return Prisma.sql([
`to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${POSTGRESQL_DATE_FORMATS[unit]}')`,
]);
}
return Prisma.sql([
`to_char(date_trunc('${unit}', ${field}), '${POSTGRESQL_DATE_FORMATS[unit]}')`,
]);
}
export function getTimestampIntervalQuery(field: string) {
return Prisma.sql([
`floor(extract(epoch from max(${field}) - min(${field})))`,
]);
}