diff --git a/lib/auth.js b/lib/auth.js index e22e5f2c..7f570031 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,6 +1,6 @@ import { parseSecureToken, parseToken } from './crypto'; import { SHARE_TOKEN_HEADER } from './constants'; -import { getWebsiteById } from './queries'; +import { getWebsiteById } from 'queries'; export async function getAuthToken(req) { try { diff --git a/lib/queries.js b/lib/queries.js index c21e20ef..98fc6aa3 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -1,13 +1,6 @@ -import moment from 'moment-timezone'; +import { MYSQL, MYSQL_DATE_FORMATS, POSTGRESQL, POSTGRESQL_DATE_FORMATS } from 'lib/constants'; import prisma from 'lib/db'; -import { subMinutes } from 'date-fns'; -import { - MYSQL, - POSTGRESQL, - MYSQL_DATE_FORMATS, - POSTGRESQL_DATE_FORMATS, - URL_LENGTH, -} from 'lib/constants'; +import moment from 'moment-timezone'; export function getDatabase() { const type = @@ -160,459 +153,3 @@ export async function rawQuery(query, params = []) { return runQuery(prisma.$queryRawUnsafe.apply(prisma, [sql, ...params])); } - -export async function getWebsiteById(website_id) { - return runQuery( - prisma.website.findUnique({ - where: { - website_id, - }, - }), - ); -} - -export async function getWebsiteByUuid(website_uuid) { - return runQuery( - prisma.website.findUnique({ - where: { - website_uuid, - }, - }), - ); -} - -export async function getWebsiteByShareId(share_id) { - return runQuery( - prisma.website.findUnique({ - where: { - share_id, - }, - }), - ); -} - -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 })); -} - -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}`); -} - -export async function deleteWebsite(website_id) { - return runQuery( - prisma.website.delete({ - where: { - website_id, - }, - }), - ); -} - -export async function createSession(website_id, data) { - return runQuery( - prisma.session.create({ - data: { - website_id, - ...data, - }, - select: { - session_id: true, - }, - }), - ); -} - -export async function getSessionByUuid(session_uuid) { - return runQuery( - prisma.session.findUnique({ - where: { - session_uuid, - }, - }), - ); -} - -export async function savePageView(website_id, session_id, url, referrer) { - return runQuery( - prisma.pageview.create({ - data: { - website_id, - session_id, - url: url?.substr(0, URL_LENGTH), - referrer: referrer?.substr(0, URL_LENGTH), - }, - }), - ); -} - -export async function saveEvent(website_id, session_id, url, event_type, event_value) { - return runQuery( - prisma.event.create({ - data: { - website_id, - session_id, - url: url?.substr(0, URL_LENGTH), - event_type: event_type?.substr(0, 50), - event_value: event_value?.substr(0, 50), - }, - }), - ); -} - -export async function getAccounts() { - return runQuery( - prisma.account.findMany({ - orderBy: [ - { is_admin: 'desc' }, - { - username: 'asc', - }, - ], - select: { - user_id: true, - username: true, - is_admin: true, - created_at: true, - updated_at: true, - }, - }), - ); -} - -export async function getAccountById(user_id) { - return runQuery( - prisma.account.findUnique({ - where: { - user_id, - }, - }), - ); -} - -export async function getAccountByUsername(username) { - return runQuery( - prisma.account.findUnique({ - 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.account.delete({ - where: { - user_id, - }, - }), - ); -} - -export async function createAccount(data) { - return runQuery( - prisma.account.create({ - data, - }), - ); -} - -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 = {}) { - const params = [website_id, start_at, end_at]; - const { pageviewQuery, sessionQuery, joinSession } = parseFilters('pageview', filters, params); - - return rawQuery( - ` - select sum(t.c) as "pageviews", - count(distinct t.session_id) as "uniques", - sum(case when t.c = 1 then 1 else 0 end) as "bounces", - sum(t.time) as "totaltime" - from ( - select pageview.session_id, - ${getDateQuery('pageview.created_at', 'hour', false, 'date')}, - count(*) c, - ${getTimestampInterval('pageview.created_at')} as "time" - from pageview - ${joinSession} - where pageview.website_id=$1 - and pageview.created_at between $2 and $3 - ${pageviewQuery} - ${sessionQuery} - group by 1, 2 - ) t - `, - params, - ); -} - -export function getPageviewStats( - website_id, - start_at, - end_at, - timezone = 'utc', - unit = 'day', - count = '*', - filters = {}, -) { - const params = [website_id, start_at, end_at]; - const { pageviewQuery, sessionQuery, joinSession } = parseFilters('pageview', filters, params); - - return rawQuery( - ` - select - ${getDateStringQuery('g.t', unit)} as t, - g.y as y - from - (select ${getDateQuery('pageview.created_at', unit, timezone)} t, - count(${count}) y - from pageview - ${joinSession} - where pageview.website_id=$1 - and pageview.created_at between $2 and $3 - ${pageviewQuery} - ${sessionQuery} - group by 1) g - order by 1 - `, - params, - ); -} - -export function getSessionMetrics(website_id, start_at, end_at, field, filters = {}) { - const params = [website_id, start_at, end_at]; - const { pageviewQuery, sessionQuery, joinSession } = parseFilters('pageview', filters, params); - - return rawQuery( - ` - select ${field} x, count(*) y - from session as x - where x.session_id in ( - select pageview.session_id - from pageview - ${joinSession} - where pageview.website_id=$1 - and pageview.created_at between $2 and $3 - ${pageviewQuery} - ${sessionQuery} - ) - group by 1 - order by 2 desc - `, - params, - ); -} - -export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) { - const params = [website_id, start_at, end_at]; - const { pageviewQuery, sessionQuery, eventQuery, joinSession } = parseFilters( - table, - filters, - params, - ); - - return rawQuery( - ` - select ${field} x, count(*) y - from ${table} - ${joinSession} - where ${table}.website_id=$1 - and ${table}.created_at between $2 and $3 - ${pageviewQuery} - ${joinSession && sessionQuery} - ${eventQuery} - group by 1 - order by 2 desc - `, - params, - ); -} - -export function getActiveVisitors(website_id) { - const date = subMinutes(new Date(), 5); - const params = [website_id, date]; - - return rawQuery( - ` - select count(distinct session_id) x - from pageview - where website_id=$1 - and created_at >= $2 - `, - params, - ); -} - -export function getEventMetrics( - website_id, - start_at, - end_at, - timezone = 'utc', - unit = 'day', - filters = {}, -) { - const params = [website_id, start_at, end_at]; - - return rawQuery( - ` - select - event_value x, - ${getDateStringQuery(getDateQuery('created_at', unit, timezone), unit)} t, - count(*) y - from event - where website_id=$1 - and created_at between $2 and $3 - ${getFilterQuery('event', filters, params)} - group by 1, 2 - order by 2 - `, - params, - ); -} - -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(), - }; -} diff --git a/lib/session.js b/lib/session.js index a08eec79..15a349fd 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,4 +1,4 @@ -import { getWebsiteByUuid, getSessionByUuid, createSession } from 'lib/queries'; +import { getWebsiteByUuid, getSessionByUuid, createSession } from 'queries'; import { getJsonBody, getClientInfo } from 'lib/request'; import { uuid, isValidUuid, parseToken } from 'lib/crypto'; diff --git a/pages/api/account/[id].js b/pages/api/account/[id].js index 6f9beac1..51279f4b 100644 --- a/pages/api/account/[id].js +++ b/pages/api/account/[id].js @@ -1,4 +1,4 @@ -import { getAccountById, deleteAccount } from 'lib/queries'; +import { getAccountById, deleteAccount } from 'queries'; import { useAuth } from 'lib/middleware'; import { methodNotAllowed, ok, unauthorized } from 'lib/response'; diff --git a/pages/api/account/index.js b/pages/api/account/index.js index 5afd8212..a7cb3795 100644 --- a/pages/api/account/index.js +++ b/pages/api/account/index.js @@ -1,4 +1,4 @@ -import { getAccountById, getAccountByUsername, updateAccount, createAccount } from 'lib/queries'; +import { getAccountById, getAccountByUsername, updateAccount, createAccount } from 'queries'; import { useAuth } from 'lib/middleware'; import { hashPassword } from 'lib/crypto'; import { ok, unauthorized, methodNotAllowed, badRequest } from 'lib/response'; diff --git a/pages/api/account/password.js b/pages/api/account/password.js index 39068c7f..432ba34c 100644 --- a/pages/api/account/password.js +++ b/pages/api/account/password.js @@ -1,4 +1,4 @@ -import { getAccountById, updateAccount } from 'lib/queries'; +import { getAccountById, updateAccount } from 'queries'; import { useAuth } from 'lib/middleware'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'lib/response'; import { checkPassword, hashPassword } from 'lib/crypto'; diff --git a/pages/api/accounts/index.js b/pages/api/accounts/index.js index 3d651601..29edf828 100644 --- a/pages/api/accounts/index.js +++ b/pages/api/accounts/index.js @@ -1,4 +1,4 @@ -import { getAccounts } from 'lib/queries'; +import { getAccounts } from 'queries'; import { useAuth } from 'lib/middleware'; import { ok, unauthorized, methodNotAllowed } from 'lib/response'; diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js index 11d81ba6..9a4aed9e 100644 --- a/pages/api/auth/login.js +++ b/pages/api/auth/login.js @@ -1,5 +1,5 @@ import { checkPassword, createSecureToken } from 'lib/crypto'; -import { getAccountByUsername } from 'lib/queries'; +import { getAccountByUsername } from 'queries/admin/account/getAccountByUsername'; import { ok, unauthorized, badRequest } from 'lib/response'; export default async (req, res) => { diff --git a/pages/api/collect.js b/pages/api/collect.js index 3387cde1..acd031a9 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -1,7 +1,7 @@ const { Resolver } = require('dns').promises; import isbot from 'isbot'; import ipaddr from 'ipaddr.js'; -import { savePageView, saveEvent } from 'lib/queries'; +import { savePageView, saveEvent } from 'queries'; import { useCors, useSession } from 'lib/middleware'; import { getJsonBody, getIpAddress } from 'lib/request'; import { ok, send, badRequest, forbidden } from 'lib/response'; diff --git a/pages/api/realtime/init.js b/pages/api/realtime/init.js index 52e66600..69c70a82 100644 --- a/pages/api/realtime/init.js +++ b/pages/api/realtime/init.js @@ -1,7 +1,7 @@ import { subMinutes } from 'date-fns'; import { useAuth } from 'lib/middleware'; import { ok, methodNotAllowed } from 'lib/response'; -import { getUserWebsites, getRealtimeData } from 'lib/queries'; +import { getUserWebsites, getRealtimeData } from 'queries'; import { createToken } from 'lib/crypto'; export default async (req, res) => { diff --git a/pages/api/realtime/update.js b/pages/api/realtime/update.js index a43d7b66..4fa0ea3b 100644 --- a/pages/api/realtime/update.js +++ b/pages/api/realtime/update.js @@ -1,6 +1,6 @@ import { useAuth } from 'lib/middleware'; import { ok, methodNotAllowed, badRequest } from 'lib/response'; -import { getRealtimeData } from 'lib/queries'; +import { getRealtimeData } from 'queries'; import { parseToken } from 'lib/crypto'; import { SHARE_TOKEN_HEADER } from 'lib/constants'; diff --git a/pages/api/share/[id].js b/pages/api/share/[id].js index 461e008e..698f1ba0 100644 --- a/pages/api/share/[id].js +++ b/pages/api/share/[id].js @@ -1,4 +1,4 @@ -import { getWebsiteByShareId } from 'lib/queries'; +import { getWebsiteByShareId } from 'queries'; import { ok, notFound, methodNotAllowed } from 'lib/response'; import { createToken } from 'lib/crypto'; diff --git a/pages/api/website/[id]/active.js b/pages/api/website/[id]/active.js index ad7f8991..4acc97f1 100644 --- a/pages/api/website/[id]/active.js +++ b/pages/api/website/[id]/active.js @@ -1,7 +1,7 @@ -import { getActiveVisitors } from 'lib/queries'; import { methodNotAllowed, ok, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; +import { getActiveVisitors } from 'queries'; export default async (req, res) => { if (req.method === 'GET') { diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js index 974e79f3..e4fc1e2a 100644 --- a/pages/api/website/[id]/events.js +++ b/pages/api/website/[id]/events.js @@ -1,5 +1,5 @@ import moment from 'moment-timezone'; -import { getEventMetrics } from 'lib/queries'; +import { getEventMetrics } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/index.js b/pages/api/website/[id]/index.js index 7018a01d..048b5ff1 100644 --- a/pages/api/website/[id]/index.js +++ b/pages/api/website/[id]/index.js @@ -1,4 +1,4 @@ -import { deleteWebsite, getWebsiteById } from 'lib/queries'; +import { deleteWebsite, getWebsiteById } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js index 54c5bf3e..3fb33f88 100644 --- a/pages/api/website/[id]/metrics.js +++ b/pages/api/website/[id]/metrics.js @@ -1,4 +1,4 @@ -import { getPageviewMetrics, getSessionMetrics, getWebsiteById } from 'lib/queries'; +import { getPageviewMetrics, getSessionMetrics, getWebsiteById } from 'queries'; import { ok, methodNotAllowed, unauthorized, badRequest } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js index bc663ce1..78145e8b 100644 --- a/pages/api/website/[id]/pageviews.js +++ b/pages/api/website/[id]/pageviews.js @@ -1,5 +1,5 @@ import moment from 'moment-timezone'; -import { getPageviewStats } from 'lib/queries'; +import { getPageviewStats } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/reset.js b/pages/api/website/[id]/reset.js index 2830ccc8..10fc5cb7 100644 --- a/pages/api/website/[id]/reset.js +++ b/pages/api/website/[id]/reset.js @@ -1,4 +1,4 @@ -import { resetWebsite } from 'lib/queries'; +import { resetWebsite } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; diff --git a/pages/api/website/[id]/stats.js b/pages/api/website/[id]/stats.js index 15cc45ad..7f37574c 100644 --- a/pages/api/website/[id]/stats.js +++ b/pages/api/website/[id]/stats.js @@ -1,4 +1,4 @@ -import { getWebsiteStats } from 'lib/queries'; +import { getWebsiteStats } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/index.js b/pages/api/website/index.js index dab9a94d..59d0a5f1 100644 --- a/pages/api/website/index.js +++ b/pages/api/website/index.js @@ -1,4 +1,4 @@ -import { updateWebsite, createWebsite, getWebsiteById } from 'lib/queries'; +import { updateWebsite, createWebsite, getWebsiteById } from 'queries'; import { useAuth } from 'lib/middleware'; import { uuid, getRandomChars } from 'lib/crypto'; import { ok, unauthorized, methodNotAllowed } from 'lib/response'; diff --git a/pages/api/websites/index.js b/pages/api/websites/index.js index 589b7315..b70272d8 100644 --- a/pages/api/websites/index.js +++ b/pages/api/websites/index.js @@ -1,4 +1,4 @@ -import { getAllWebsites, getUserWebsites } from 'lib/queries'; +import { getAllWebsites, getUserWebsites } from 'queries'; import { useAuth } from 'lib/middleware'; import { ok, methodNotAllowed, unauthorized } from 'lib/response'; diff --git a/queries/admin/account/createAccount.js b/queries/admin/account/createAccount.js new file mode 100644 index 00000000..2902e526 --- /dev/null +++ b/queries/admin/account/createAccount.js @@ -0,0 +1,10 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function createAccount(data) { + return runQuery( + prisma.account.create({ + data, + }), + ); +} diff --git a/queries/admin/account/deleteAccount.js b/queries/admin/account/deleteAccount.js new file mode 100644 index 00000000..242b851c --- /dev/null +++ b/queries/admin/account/deleteAccount.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function deleteAccount(user_id) { + return runQuery( + prisma.account.delete({ + where: { + user_id, + }, + }), + ); +} diff --git a/queries/admin/account/getAccountById.js b/queries/admin/account/getAccountById.js new file mode 100644 index 00000000..bb3ae85f --- /dev/null +++ b/queries/admin/account/getAccountById.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getAccountById(user_id) { + return runQuery( + prisma.account.findUnique({ + where: { + user_id, + }, + }), + ); +} diff --git a/queries/admin/account/getAccountByUsername.js b/queries/admin/account/getAccountByUsername.js new file mode 100644 index 00000000..5e786a2a --- /dev/null +++ b/queries/admin/account/getAccountByUsername.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getAccountByUsername(username) { + return runQuery( + prisma.account.findUnique({ + where: { + username, + }, + }), + ); +} diff --git a/queries/admin/account/getAccounts.js b/queries/admin/account/getAccounts.js new file mode 100644 index 00000000..647e3626 --- /dev/null +++ b/queries/admin/account/getAccounts.js @@ -0,0 +1,22 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getAccounts() { + return runQuery( + prisma.account.findMany({ + orderBy: [ + { is_admin: 'desc' }, + { + username: 'asc', + }, + ], + select: { + user_id: true, + username: true, + is_admin: true, + created_at: true, + updated_at: true, + }, + }), + ); +} diff --git a/queries/admin/account/updateAccount.js b/queries/admin/account/updateAccount.js new file mode 100644 index 00000000..3fd252dd --- /dev/null +++ b/queries/admin/account/updateAccount.js @@ -0,0 +1,13 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function updateAccount(user_id, data) { + return runQuery( + prisma.account.update({ + where: { + user_id, + }, + data, + }), + ); +} diff --git a/queries/admin/website/createWebsite.js b/queries/admin/website/createWebsite.js new file mode 100644 index 00000000..a0b33a98 --- /dev/null +++ b/queries/admin/website/createWebsite.js @@ -0,0 +1,17 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function createWebsite(user_id, data) { + return runQuery( + prisma.website.create({ + data: { + account: { + connect: { + user_id, + }, + }, + ...data, + }, + }), + ); +} diff --git a/queries/admin/website/deleteWebsite.js b/queries/admin/website/deleteWebsite.js new file mode 100644 index 00000000..0bdd3491 --- /dev/null +++ b/queries/admin/website/deleteWebsite.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function deleteWebsite(website_id) { + return runQuery( + prisma.website.delete({ + where: { + website_id, + }, + }), + ); +} diff --git a/queries/admin/website/getAllWebsites.js b/queries/admin/website/getAllWebsites.js new file mode 100644 index 00000000..5cc12cba --- /dev/null +++ b/queries/admin/website/getAllWebsites.js @@ -0,0 +1,25 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +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 })); +} diff --git a/queries/admin/website/getUserWebsites.js b/queries/admin/website/getUserWebsites.js new file mode 100644 index 00000000..28619417 --- /dev/null +++ b/queries/admin/website/getUserWebsites.js @@ -0,0 +1,15 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getUserWebsites(user_id) { + return runQuery( + prisma.website.findMany({ + where: { + user_id, + }, + orderBy: { + name: 'asc', + }, + }), + ); +} diff --git a/queries/admin/website/getWebsiteById.js b/queries/admin/website/getWebsiteById.js new file mode 100644 index 00000000..ad2d9b11 --- /dev/null +++ b/queries/admin/website/getWebsiteById.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getWebsiteById(website_id) { + return runQuery( + prisma.website.findUnique({ + where: { + website_id, + }, + }), + ); +} diff --git a/queries/admin/website/getWebsiteByShareId.js b/queries/admin/website/getWebsiteByShareId.js new file mode 100644 index 00000000..d6074e09 --- /dev/null +++ b/queries/admin/website/getWebsiteByShareId.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getWebsiteByShareId(share_id) { + return runQuery( + prisma.website.findUnique({ + where: { + share_id, + }, + }), + ); +} diff --git a/queries/admin/website/getWebsiteByUuid.js b/queries/admin/website/getWebsiteByUuid.js new file mode 100644 index 00000000..58ada3a3 --- /dev/null +++ b/queries/admin/website/getWebsiteByUuid.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getWebsiteByUuid(website_uuid) { + return runQuery( + prisma.website.findUnique({ + where: { + website_uuid, + }, + }), + ); +} diff --git a/queries/admin/website/resetWebsite.js b/queries/admin/website/resetWebsite.js new file mode 100644 index 00000000..45ce2315 --- /dev/null +++ b/queries/admin/website/resetWebsite.js @@ -0,0 +1,6 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function resetWebsite(website_id) { + return runQuery(prisma.$queryRaw`delete from session where website_id=${website_id}`); +} diff --git a/queries/admin/website/updateWebsite.js b/queries/admin/website/updateWebsite.js new file mode 100644 index 00000000..29561bb2 --- /dev/null +++ b/queries/admin/website/updateWebsite.js @@ -0,0 +1,13 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function updateWebsite(website_id, data) { + return runQuery( + prisma.website.update({ + where: { + website_id, + }, + data, + }), + ); +} diff --git a/queries/analytics/event/getEventMetrics.js b/queries/analytics/event/getEventMetrics.js new file mode 100644 index 00000000..1bbc3cac --- /dev/null +++ b/queries/analytics/event/getEventMetrics.js @@ -0,0 +1,28 @@ +import { getDateQuery, getDateStringQuery, getFilterQuery, rawQuery } from 'lib/queries'; + +export function getEventMetrics( + website_id, + start_at, + end_at, + timezone = 'utc', + unit = 'day', + filters = {}, +) { + const params = [website_id, start_at, end_at]; + + return rawQuery( + ` + select + event_value x, + ${getDateStringQuery(getDateQuery('created_at', unit, timezone), unit)} t, + count(*) y + from event + where website_id=$1 + and created_at between $2 and $3 + ${getFilterQuery('event', filters, params)} + group by 1, 2 + order by 2 + `, + params, + ); +} diff --git a/queries/analytics/event/getEvents.js b/queries/analytics/event/getEvents.js new file mode 100644 index 00000000..9d8671db --- /dev/null +++ b/queries/analytics/event/getEvents.js @@ -0,0 +1,19 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getEvents(websites, start_at) { + return runQuery( + prisma.event.findMany({ + where: { + website: { + website_id: { + in: websites, + }, + }, + created_at: { + gte: start_at, + }, + }, + }), + ); +} diff --git a/queries/analytics/event/saveEvent.js b/queries/analytics/event/saveEvent.js new file mode 100644 index 00000000..8f5bf8d2 --- /dev/null +++ b/queries/analytics/event/saveEvent.js @@ -0,0 +1,17 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; +import { URL_LENGTH } from 'lib/constants'; + +export async function saveEvent(website_id, session_id, url, event_type, event_value) { + return runQuery( + prisma.event.create({ + data: { + website_id, + session_id, + url: url?.substr(0, URL_LENGTH), + event_type: event_type?.substr(0, 50), + event_value: event_value?.substr(0, 50), + }, + }), + ); +} diff --git a/queries/analytics/pageview/getPageviewMetrics.js b/queries/analytics/pageview/getPageviewMetrics.js new file mode 100644 index 00000000..12667706 --- /dev/null +++ b/queries/analytics/pageview/getPageviewMetrics.js @@ -0,0 +1,26 @@ +import { parseFilters, rawQuery } from 'lib/queries'; + +export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) { + const params = [website_id, start_at, end_at]; + const { pageviewQuery, sessionQuery, eventQuery, joinSession } = parseFilters( + table, + filters, + params, + ); + + return rawQuery( + ` + select ${field} x, count(*) y + from ${table} + ${joinSession} + where ${table}.website_id=$1 + and ${table}.created_at between $2 and $3 + ${pageviewQuery} + ${joinSession && sessionQuery} + ${eventQuery} + group by 1 + order by 2 desc + `, + params, + ); +} diff --git a/queries/analytics/pageview/getPageviewStats.js b/queries/analytics/pageview/getPageviewStats.js new file mode 100644 index 00000000..e96b1824 --- /dev/null +++ b/queries/analytics/pageview/getPageviewStats.js @@ -0,0 +1,34 @@ +import { parseFilters, rawQuery, getDateQuery, getDateStringQuery } from 'lib/queries'; + +export function getPageviewStats( + website_id, + start_at, + end_at, + timezone = 'utc', + unit = 'day', + count = '*', + filters = {}, +) { + const params = [website_id, start_at, end_at]; + const { pageviewQuery, sessionQuery, joinSession } = parseFilters('pageview', filters, params); + + return rawQuery( + ` + select + ${getDateStringQuery('g.t', unit)} as t, + g.y as y + from + (select ${getDateQuery('pageview.created_at', unit, timezone)} t, + count(${count}) y + from pageview + ${joinSession} + where pageview.website_id=$1 + and pageview.created_at between $2 and $3 + ${pageviewQuery} + ${sessionQuery} + group by 1) g + order by 1 + `, + params, + ); +} diff --git a/queries/analytics/pageview/getPageviews.js b/queries/analytics/pageview/getPageviews.js new file mode 100644 index 00000000..8fffd539 --- /dev/null +++ b/queries/analytics/pageview/getPageviews.js @@ -0,0 +1,19 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getPageviews(websites, start_at) { + return runQuery( + prisma.pageview.findMany({ + where: { + website: { + website_id: { + in: websites, + }, + }, + created_at: { + gte: start_at, + }, + }, + }), + ); +} diff --git a/queries/analytics/pageview/savePageView.js b/queries/analytics/pageview/savePageView.js new file mode 100644 index 00000000..427c6143 --- /dev/null +++ b/queries/analytics/pageview/savePageView.js @@ -0,0 +1,16 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; +import { URL_LENGTH } from 'lib/constants'; + +export async function savePageView(website_id, session_id, url, referrer) { + return runQuery( + prisma.pageview.create({ + data: { + website_id, + session_id, + url: url?.substr(0, URL_LENGTH), + referrer: referrer?.substr(0, URL_LENGTH), + }, + }), + ); +} diff --git a/queries/analytics/session/createSession.js b/queries/analytics/session/createSession.js new file mode 100644 index 00000000..b6e8e950 --- /dev/null +++ b/queries/analytics/session/createSession.js @@ -0,0 +1,16 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function createSession(website_id, data) { + return runQuery( + prisma.session.create({ + data: { + website_id, + ...data, + }, + select: { + session_id: true, + }, + }), + ); +} diff --git a/queries/analytics/session/getSessionByUuid.js b/queries/analytics/session/getSessionByUuid.js new file mode 100644 index 00000000..1d643efa --- /dev/null +++ b/queries/analytics/session/getSessionByUuid.js @@ -0,0 +1,12 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getSessionByUuid(session_uuid) { + return runQuery( + prisma.session.findUnique({ + where: { + session_uuid, + }, + }), + ); +} diff --git a/queries/analytics/session/getSessionMetrics.js b/queries/analytics/session/getSessionMetrics.js new file mode 100644 index 00000000..356163e9 --- /dev/null +++ b/queries/analytics/session/getSessionMetrics.js @@ -0,0 +1,25 @@ +import { parseFilters, rawQuery } from 'lib/queries'; + +export function getSessionMetrics(website_id, start_at, end_at, field, filters = {}) { + const params = [website_id, start_at, end_at]; + const { pageviewQuery, sessionQuery, joinSession } = parseFilters('pageview', filters, params); + + return rawQuery( + ` + select ${field} x, count(*) y + from session as x + where x.session_id in ( + select pageview.session_id + from pageview + ${joinSession} + where pageview.website_id=$1 + and pageview.created_at between $2 and $3 + ${pageviewQuery} + ${sessionQuery} + ) + group by 1 + order by 2 desc + `, + params, + ); +} diff --git a/queries/analytics/session/getSessions.js b/queries/analytics/session/getSessions.js new file mode 100644 index 00000000..450d1ad2 --- /dev/null +++ b/queries/analytics/session/getSessions.js @@ -0,0 +1,19 @@ +import { runQuery } from 'lib/queries'; +import prisma from 'lib/db'; + +export async function getSessions(websites, start_at) { + return runQuery( + prisma.session.findMany({ + where: { + website: { + website_id: { + in: websites, + }, + }, + created_at: { + gte: start_at, + }, + }, + }), + ); +} diff --git a/queries/analytics/stats/getActiveVisitors.js b/queries/analytics/stats/getActiveVisitors.js new file mode 100644 index 00000000..3f923311 --- /dev/null +++ b/queries/analytics/stats/getActiveVisitors.js @@ -0,0 +1,17 @@ +import { rawQuery } from 'lib/queries'; +import { subMinutes } from 'date-fns'; + +export function getActiveVisitors(website_id) { + const date = subMinutes(new Date(), 5); + const params = [website_id, date]; + + return rawQuery( + ` + select count(distinct session_id) x + from pageview + where website_id=$1 + and created_at >= $2 + `, + params, + ); +} diff --git a/queries/analytics/stats/getRealtimeData.js b/queries/analytics/stats/getRealtimeData.js new file mode 100644 index 00000000..313d757d --- /dev/null +++ b/queries/analytics/stats/getRealtimeData.js @@ -0,0 +1,30 @@ +import { getPageviews } from '../pageview/getPageviews'; +import { getSessions } from '../session/getSessions'; +import { getEvents } from '../event/getEvents'; + +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(), + }; +} diff --git a/queries/analytics/stats/getWebsiteStats.js b/queries/analytics/stats/getWebsiteStats.js new file mode 100644 index 00000000..19eb3340 --- /dev/null +++ b/queries/analytics/stats/getWebsiteStats.js @@ -0,0 +1,29 @@ +import { parseFilters, rawQuery, getDateQuery, getTimestampInterval } from 'lib/queries'; + +export function getWebsiteStats(website_id, start_at, end_at, filters = {}) { + const params = [website_id, start_at, end_at]; + const { pageviewQuery, sessionQuery, joinSession } = parseFilters('pageview', filters, params); + + return rawQuery( + ` + select sum(t.c) as "pageviews", + count(distinct t.session_id) as "uniques", + sum(case when t.c = 1 then 1 else 0 end) as "bounces", + sum(t.time) as "totaltime" + from ( + select pageview.session_id, + ${getDateQuery('pageview.created_at', 'hour')}, + count(*) c, + ${getTimestampInterval('pageview.created_at')} as "time" + from pageview + ${joinSession} + where pageview.website_id=$1 + and pageview.created_at between $2 and $3 + ${pageviewQuery} + ${sessionQuery} + group by 1, 2 + ) t + `, + params, + ); +} diff --git a/queries/index.js b/queries/index.js new file mode 100644 index 00000000..a3ea9d57 --- /dev/null +++ b/queries/index.js @@ -0,0 +1,61 @@ +import { createAccount } from './admin/account/createAccount'; +import { deleteAccount } from './admin/account/deleteAccount'; +import { getAccountById } from './admin/account/getAccountById'; +import { getAccountByUsername } from './admin/account/getAccountByUsername'; +import { getAccounts } from './admin/account/getAccounts'; +import { updateAccount } from './admin/account/updateAccount'; +import { createWebsite } from './admin/website/createWebsite'; +import { deleteWebsite } from './admin/website/deleteWebsite'; +import { getAllWebsites } from './admin/website/getAllWebsites'; +import { getUserWebsites } from './admin/website/getUserWebsites'; +import { getWebsiteById } from './admin/website/getWebsiteById'; +import { getWebsiteByShareId } from './admin/website/getWebsiteByShareId'; +import { getWebsiteByUuid } from './admin/website/getWebsiteByUuid'; +import { resetWebsite } from './admin/website/resetWebsite'; +import { updateWebsite } from './admin/website/updateWebsite'; +import { getEventMetrics } from './analytics/event/getEventMetrics'; +import { getEvents } from './analytics/event/getEvents'; +import { saveEvent } from './analytics/event/saveEvent'; +import { getPageviewMetrics } from './analytics/pageview/getPageviewMetrics'; +import { getPageviews } from './analytics/pageview/getPageviews'; +import { getPageviewStats } from './analytics/pageview/getPageviewStats'; +import { savePageView } from './analytics/pageview/savePageView'; +import { createSession } from './analytics/session/createSession'; +import { getSessionByUuid } from './analytics/session/getSessionByUuid'; +import { getSessionMetrics } from './analytics/session/getSessionMetrics'; +import { getSessions } from './analytics/session/getSessions'; +import { getActiveVisitors } from './analytics/stats/getActiveVisitors'; +import { getRealtimeData } from './analytics/stats/getRealtimeData'; +import { getWebsiteStats } from './analytics/stats/getWebsiteStats'; + +export { + createWebsite, + deleteWebsite, + getAllWebsites, + getUserWebsites, + getWebsiteById, + getWebsiteByShareId, + getWebsiteByUuid, + resetWebsite, + updateWebsite, + createAccount, + deleteAccount, + getAccountById, + getAccountByUsername, + getAccounts, + updateAccount, + getEventMetrics, + getEvents, + saveEvent, + getPageviewMetrics, + getPageviews, + getPageviewStats, + savePageView, + createSession, + getSessionByUuid, + getSessionMetrics, + getSessions, + getActiveVisitors, + getRealtimeData, + getWebsiteStats, +};