umami/lib/queries.js

566 lines
11 KiB
JavaScript
Raw Normal View History

2020-08-12 05:05:40 +02:00
import moment from 'moment-timezone';
2020-09-25 08:02:11 +02:00
import prisma from 'lib/db';
2020-08-18 09:51:32 +02:00
import { subMinutes } from 'date-fns';
2021-03-13 08:45:19 +01:00
import {
MYSQL,
POSTGRESQL,
MYSQL_DATE_FORMATS,
POSTGRESQL_DATE_FORMATS,
URL_LENGTH,
} from 'lib/constants';
2020-08-12 05:05:40 +02:00
export function getDatabase() {
const type =
2020-08-30 09:02:03 +02:00
process.env.DATABASE_TYPE ||
(process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]);
if (type === 'postgres') {
return 'postgresql';
}
return type;
2020-08-12 05:05:40 +02:00
}
2020-09-25 08:02:11 +02:00
export async function runQuery(query) {
2020-10-07 17:31:44 +02:00
return query.catch(e => {
throw e;
});
2020-09-25 08:02:11 +02:00
}
2020-10-02 02:32:49 +02:00
export async function rawQuery(query, params = []) {
2020-09-25 08:02:11 +02:00
const db = getDatabase();
if (db !== POSTGRESQL && db !== MYSQL) {
return Promise.reject(new Error('Unknown database.'));
}
const sql = db === MYSQL ? query.replace(/\$[0-9]+/g, '?') : query;
return prisma.$queryRaw.apply(prisma, [sql, ...params]);
}
export function getDateQuery(field, unit, timezone) {
const db = getDatabase();
if (db === POSTGRESQL) {
if (timezone) {
return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
}
return `to_char(date_trunc('${unit}', ${field}), '${POSTGRESQL_DATE_FORMATS[unit]}')`;
}
if (db === MYSQL) {
if (timezone) {
const tz = moment.tz(timezone).format('Z');
return `DATE_FORMAT(convert_tz(${field},'+00:00','${tz}'), '${MYSQL_DATE_FORMATS[unit]}')`;
}
return `DATE_FORMAT(${field}, '${MYSQL_DATE_FORMATS[unit]}')`;
}
}
2020-09-25 08:02:11 +02:00
export function getTimestampInterval(field) {
const db = getDatabase();
if (db === POSTGRESQL) {
return `floor(extract(epoch from max(${field}) - min(${field})))`;
}
if (db === MYSQL) {
return `floor(unix_timestamp(max(${field})) - unix_timestamp(min(${field})))`;
}
}
2020-08-12 07:24:41 +02:00
export async function getWebsiteById(website_id) {
return runQuery(
prisma.website.findUnique({
2020-08-12 07:24:41 +02:00
where: {
website_id,
},
}),
);
}
export async function getWebsiteByUuid(website_uuid) {
return runQuery(
prisma.website.findUnique({
2020-08-12 07:24:41 +02:00
where: {
website_uuid,
},
}),
);
}
2020-08-15 10:17:15 +02:00
export async function getWebsiteByShareId(share_id) {
return runQuery(
prisma.website.findUnique({
2020-08-15 10:17:15 +02:00
where: {
share_id,
},
}),
);
}
2020-08-12 07:24:41 +02:00
export async function getUserWebsites(user_id) {
return runQuery(
prisma.website.findMany({
where: {
user_id,
},
orderBy: {
name: 'asc',
},
}),
);
}
export async function getAllWebsites() {
let data = await runQuery(
prisma.website.findMany({
orderBy: [
{
user_id: 'asc',
},
{
name: 'asc',
},
],
include: {
account: {
select: {
username: true,
},
},
},
}),
);
return data.map(i => ({ ...i, account: i.account.username }));
}
2020-08-12 07:24:41 +02:00
export async function createWebsite(user_id, data) {
return runQuery(
prisma.website.create({
data: {
account: {
connect: {
user_id,
},
},
...data,
},
}),
);
}
export async function updateWebsite(website_id, data) {
return runQuery(
prisma.website.update({
where: {
website_id,
},
data,
}),
);
}
export async function resetWebsite(website_id) {
return runQuery(prisma.$queryRaw`delete from session where website_id=${website_id}`);
}
2020-08-12 07:24:41 +02:00
export async function deleteWebsite(website_id) {
return runQuery(
/* Prisma bug, does not cascade on non-nullable foreign keys
prisma.website.delete({
where: {
website_id,
},
}),
*/
prisma.$queryRaw`delete from website where website_id=${website_id}`,
);
}
export async function createSession(website_id, data) {
return runQuery(
prisma.session.create({
data: {
2021-03-25 13:44:37 +01:00
website_id,
2020-08-12 07:24:41 +02:00
...data,
},
select: {
session_id: true,
},
}),
);
}
export async function getSessionByUuid(session_uuid) {
return runQuery(
prisma.session.findUnique({
2020-08-12 07:24:41 +02:00
where: {
session_uuid,
},
}),
);
}
export async function savePageView(website_id, session_id, url, referrer) {
return runQuery(
prisma.pageview.create({
data: {
2021-03-25 13:44:37 +01:00
website_id,
session_id,
2021-03-27 04:56:08 +01:00
url: url?.substr(0, URL_LENGTH),
referrer: referrer?.substr(0, URL_LENGTH),
2020-08-12 07:24:41 +02:00
},
}),
);
}
export async function saveEvent(website_id, session_id, url, event_type, event_value) {
return runQuery(
prisma.event.create({
data: {
2021-03-25 13:44:37 +01:00
website_id,
session_id,
2021-03-27 04:56:08 +01:00
url: url?.substr(0, URL_LENGTH),
event_type: event_type?.substr(0, 50),
event_value: event_value?.substr(0, 50),
2020-08-12 07:24:41 +02:00
},
}),
);
}
export async function getAccounts() {
return runQuery(prisma.account.findMany());
}
export async function getAccountById(user_id) {
return runQuery(
prisma.account.findUnique({
2020-08-12 07:24:41 +02:00
where: {
user_id,
},
}),
);
}
export async function getAccountByUsername(username) {
return runQuery(
prisma.account.findUnique({
2020-08-12 07:24:41 +02:00
where: {
username,
},
}),
);
}
export async function updateAccount(user_id, data) {
return runQuery(
prisma.account.update({
where: {
user_id,
},
data,
}),
);
}
export async function deleteAccount(user_id) {
return runQuery(
/* Prisma bug, does not cascade on non-nullable foreign keys
prisma.account.delete({
where: {
user_id,
},
}),
*/
prisma.$queryRaw`delete from account where user_id=${user_id}`,
);
}
export async function createAccount(data) {
return runQuery(
prisma.account.create({
data,
}),
);
}
2020-10-09 08:26:05 +02:00
export async function getSessions(websites, start_at) {
return runQuery(
prisma.session.findMany({
where: {
website: {
website_id: {
in: websites,
},
},
created_at: {
gte: start_at,
},
},
}),
);
}
export async function getPageviews(websites, start_at) {
return runQuery(
prisma.pageview.findMany({
where: {
website: {
website_id: {
in: websites,
},
},
created_at: {
gte: start_at,
},
},
}),
);
}
export async function getEvents(websites, start_at) {
return runQuery(
prisma.event.findMany({
where: {
website: {
website_id: {
in: websites,
},
},
created_at: {
gte: start_at,
},
},
}),
);
}
export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
2020-09-26 07:31:18 +02:00
const params = [website_id, start_at, end_at];
2021-11-22 07:00:14 +01:00
const { url, ref } = filters;
2020-09-26 07:31:18 +02:00
let urlFilter = '';
2021-11-22 07:00:14 +01:00
let refFilter = '';
2020-09-26 07:31:18 +02:00
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
2021-11-22 07:00:14 +01:00
if (ref) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(ref)}%`);
}
2020-09-25 08:02:11 +02:00
return rawQuery(
`
2020-08-12 05:05:40 +02:00
select sum(t.c) as "pageviews",
count(distinct t.session_id) as "uniques",
2020-08-24 19:52:47 +02:00
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
2020-08-12 05:05:40 +02:00
sum(t.time) as "totaltime"
from (
select session_id,
2020-09-25 08:02:11 +02:00
${getDateQuery('created_at', 'hour')},
2020-08-12 05:05:40 +02:00
count(*) c,
2020-09-25 08:02:11 +02:00
${getTimestampInterval('created_at')} as "time"
2020-08-12 05:05:40 +02:00
from pageview
where website_id=$1
and created_at between $2 and $3
2020-09-26 07:31:18 +02:00
${urlFilter}
2021-11-22 07:00:14 +01:00
${refFilter}
2020-08-12 05:05:40 +02:00
group by 1, 2
) t
2020-09-25 08:02:11 +02:00
`,
2020-09-26 07:31:18 +02:00
params,
2020-09-25 08:02:11 +02:00
);
2020-08-12 05:05:40 +02:00
}
export function getPageviewStats(
2020-08-12 07:24:41 +02:00
website_id,
start_at,
end_at,
timezone = 'utc',
unit = 'day',
count = '*',
2021-11-22 07:00:14 +01:00
filters = {},
2020-08-12 07:24:41 +02:00
) {
2020-09-26 07:31:18 +02:00
const params = [website_id, start_at, end_at];
2021-11-22 07:00:14 +01:00
const { url, ref } = filters;
2020-09-26 07:31:18 +02:00
let urlFilter = '';
2021-11-22 07:00:14 +01:00
let refFilter = '';
2020-09-26 07:31:18 +02:00
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
2021-11-22 07:00:14 +01:00
if (ref) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(ref)}%`);
}
2020-09-25 08:02:11 +02:00
return rawQuery(
`
select ${getDateQuery('created_at', unit, timezone)} t,
count(${count}) y
from pageview
where website_id=$1
and created_at between $2 and $3
2020-09-26 07:31:18 +02:00
${urlFilter}
2021-11-22 07:00:14 +01:00
${refFilter}
2020-09-25 08:02:11 +02:00
group by 1
order by 1
`,
2020-09-26 07:31:18 +02:00
params,
2020-09-25 08:02:11 +02:00
);
2020-08-12 05:05:40 +02:00
}
export function getSessionMetrics(website_id, start_at, end_at, field, filters = {}) {
2020-09-26 07:31:18 +02:00
const params = [website_id, start_at, end_at];
const { url } = filters;
2020-09-26 07:31:18 +02:00
let urlFilter = '';
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
return rawQuery(
`
select ${field} x, count(*) y
from session
where session_id in (
select session_id
from pageview
where website_id=$1
and created_at between $2 and $3
2020-09-26 07:31:18 +02:00
${urlFilter}
)
group by 1
order by 2 desc
`,
2020-09-26 07:31:18 +02:00
params,
);
}
export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) {
2020-09-26 07:31:18 +02:00
const params = [website_id, start_at, end_at];
2021-11-22 23:53:36 +01:00
const { domain, url } = filters;
2020-09-26 07:31:18 +02:00
let domainFilter = '';
let urlFilter = '';
if (domain) {
domainFilter = `and referrer not like $${params.length + 1} and referrer not like '/%'`;
2021-02-27 04:50:44 +01:00
params.push(`%://${domain}/%`);
2020-09-26 07:31:18 +02:00
}
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
2020-09-25 08:02:11 +02:00
return rawQuery(
`
select ${field} x, count(*) y
2020-09-25 08:02:11 +02:00
from ${table}
where website_id=$1
and created_at between $2 and $3
2020-09-26 07:31:18 +02:00
${domainFilter}
${urlFilter}
2020-09-25 08:02:11 +02:00
group by 1
order by 2 desc
`,
2020-09-26 07:31:18 +02:00
params,
2020-09-25 08:02:11 +02:00
);
2020-08-12 05:05:40 +02:00
}
2020-08-18 09:51:32 +02:00
export function getActiveVisitors(website_id) {
2020-08-19 03:33:59 +02:00
const date = subMinutes(new Date(), 5);
2020-09-26 07:31:18 +02:00
const params = [website_id, date];
2020-08-19 03:33:59 +02:00
2020-09-25 08:02:11 +02:00
return rawQuery(
`
2020-08-18 09:51:32 +02:00
select count(distinct session_id) x
from pageview
where website_id=$1
and created_at >= $2
`,
2020-09-26 07:31:18 +02:00
params,
2020-09-25 08:02:11 +02:00
);
2020-08-18 09:51:32 +02:00
}
2020-08-25 08:49:14 +02:00
2020-10-09 08:26:05 +02:00
export function getEventMetrics(
website_id,
start_at,
end_at,
timezone = 'utc',
unit = 'day',
filters = {},
) {
2020-09-26 07:31:18 +02:00
const params = [website_id, start_at, end_at];
const { url, event_type } = filters;
2020-09-26 07:31:18 +02:00
let urlFilter = '';
let eventTypeFilter = '';
2020-09-26 07:31:18 +02:00
if (url) {
urlFilter = `and url=$${params.length + 1}`;
params.push(decodeURIComponent(url));
}
if (event_type) {
eventTypeFilter = `and event_type=$${params.length + 1}`;
params.push(event_type);
}
2020-09-25 08:02:11 +02:00
return rawQuery(
`
select
event_value x,
${getDateQuery('created_at', unit, timezone)} t,
count(*) y
from event
where website_id=$1
and created_at between $2 and $3
2020-09-26 07:31:18 +02:00
${urlFilter}
${eventTypeFilter}
2020-09-25 08:02:11 +02:00
group by 1, 2
order by 2
`,
2020-09-26 07:31:18 +02:00
params,
2020-09-25 08:02:11 +02:00
);
2020-08-27 12:42:24 +02:00
}
export async function getRealtimeData(websites, time) {
const [pageviews, sessions, events] = await Promise.all([
getPageviews(websites, time),
getSessions(websites, time),
getEvents(websites, time),
]);
return {
pageviews: pageviews.map(({ view_id, ...props }) => ({
__id: `p${view_id}`,
view_id,
...props,
})),
sessions: sessions.map(({ session_id, ...props }) => ({
__id: `s${session_id}`,
session_id,
...props,
})),
events: events.map(({ event_id, ...props }) => ({
__id: `e${event_id}`,
event_id,
...props,
})),
timestamp: Date.now(),
};
}