diff --git a/public/manifest/manifest.webmanifest b/public/manifest/manifest.webmanifest
deleted file mode 100644
index 3237da05..00000000
--- a/public/manifest/manifest.webmanifest
+++ /dev/null
@@ -1 +0,0 @@
-{"name":"analytics","short_name":"analytics","display":"standalone","start_url":"/","icons":[{"src":"/manifest/favicon-192.png","type":"image/png","sizes":"192x192"},{"src":"/manifest/favicon-512.png","type":"image/png","sizes":"512x512"}]}
\ No newline at end of file
diff --git a/public/manifest/site.webmanifest b/public/manifest/site.webmanifest
new file mode 100644
index 00000000..769970cf
--- /dev/null
+++ b/public/manifest/site.webmanifest
@@ -0,0 +1,10 @@
+{
+ "name": "analytics",
+ "short_name": "analytics",
+ "display": "standalone",
+ "start_url": "/",
+ "icons": [
+ { "src": "/manifest/favicon-192.png", "type": "image/png", "sizes": "192x192" },
+ { "src": "/manifest/favicon-512.png", "type": "image/png", "sizes": "512x512" }
+ ]
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 7d8b79ad..f9d14f7d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -14,7 +14,7 @@ export default function ({ children }) {
-
+
{/* */}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
deleted file mode 100644
index cb3d3cc7..00000000
--- a/src/lib/auth.ts
+++ /dev/null
@@ -1,226 +0,0 @@
-import { Report } from '@prisma/client';
-import debug from 'debug';
-import redis from '@umami/redis-client';
-import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants';
-import { secret } from 'lib/crypto';
-import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics';
-import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries';
-import { loadWebsite } from './load';
-import { Auth } from './types';
-import { NextApiRequest } from 'next';
-
-const log = debug('umami:auth');
-const cloudMode = process.env.CLOUD_MODE;
-
-export async function saveAuth(data: any, expire = 0) {
- const authKey = `auth:${getRandomChars(32)}`;
-
- await redis.client.set(authKey, data);
-
- if (expire) {
- await redis.client.expire(authKey, expire);
- }
-
- return createSecureToken({ authKey }, secret());
-}
-
-export function getAuthToken(req: NextApiRequest) {
- try {
- return req.headers.authorization.split(' ')[1];
- } catch {
- return null;
- }
-}
-
-export function parseShareToken(req: Request) {
- try {
- return parseToken(req.headers[SHARE_TOKEN_HEADER], secret());
- } catch (e) {
- log(e);
- return null;
- }
-}
-
-export async function canViewWebsite({ user, shareToken }: Auth, websiteId: string) {
- if (user?.isAdmin) {
- return true;
- }
-
- if (shareToken?.websiteId === websiteId) {
- return true;
- }
-
- const website = await loadWebsite(websiteId);
-
- if (user.id === website?.userId) {
- return true;
- }
-
- return !!(await findTeamWebsiteByUserId(websiteId, user.id));
-}
-
-export async function canViewAllWebsites({ user }: Auth) {
- return user.isAdmin;
-}
-
-export async function canCreateWebsite({ user, grant }: Auth) {
- if (cloudMode) {
- return !!grant?.find(a => a === PERMISSIONS.websiteCreate);
- }
-
- if (user.isAdmin) {
- return true;
- }
-
- return hasPermission(user.role, PERMISSIONS.websiteCreate);
-}
-
-export async function canUpdateWebsite({ user }: Auth, websiteId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- const website = await loadWebsite(websiteId);
-
- return user.id === website?.userId;
-}
-
-export async function canDeleteWebsite({ user }: Auth, websiteId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- const website = await loadWebsite(websiteId);
-
- return user.id === website?.userId;
-}
-
-export async function canViewReport(auth: Auth, report: Report) {
- if (auth.user.isAdmin) {
- return true;
- }
-
- if (auth.user.id == report.userId) {
- return true;
- }
-
- return !!(await canViewWebsite(auth, report.websiteId));
-}
-
-export async function canUpdateReport({ user }: Auth, report: Report) {
- if (user.isAdmin) {
- return true;
- }
-
- return user.id == report.userId;
-}
-
-export async function canDeleteReport(auth: Auth, report: Report) {
- return canUpdateReport(auth, report);
-}
-
-export async function canCreateTeam({ user, grant }: Auth) {
- if (cloudMode) {
- return !!grant?.find(a => a === PERMISSIONS.teamCreate);
- }
-
- if (user.isAdmin) {
- return true;
- }
-
- return !!user;
-}
-
-export async function canViewTeam({ user }: Auth, teamId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- return getTeamUser(teamId, user.id);
-}
-
-export async function canUpdateTeam({ user }: Auth, teamId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- const teamUser = await getTeamUser(teamId, user.id);
-
- return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
-}
-
-export async function canDeleteTeam({ user }: Auth, teamId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- const teamUser = await getTeamUser(teamId, user.id);
-
- return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamDelete);
-}
-
-export async function canDeleteTeamUser({ user }: Auth, teamId: string, removeUserId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- if (removeUserId === user.id) {
- return true;
- }
-
- const teamUser = await getTeamUser(teamId, user.id);
-
- return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
-}
-
-export async function canDeleteTeamWebsite({ user }: Auth, teamId: string, websiteId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- const teamWebsite = await getTeamWebsite(teamId, websiteId);
-
- if (teamWebsite?.website?.userId === user.id) {
- return true;
- }
-
- if (teamWebsite) {
- const teamUser = await getTeamUser(teamWebsite.teamId, user.id);
-
- return hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
- }
-
- return false;
-}
-
-export async function canCreateUser({ user }: Auth) {
- return user.isAdmin;
-}
-
-export async function canViewUser({ user }: Auth, viewedUserId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- return user.id === viewedUserId;
-}
-
-export async function canViewUsers({ user }: Auth) {
- return user.isAdmin;
-}
-
-export async function canUpdateUser({ user }: Auth, viewedUserId: string) {
- if (user.isAdmin) {
- return true;
- }
-
- return user.id === viewedUserId;
-}
-
-export async function canDeleteUser({ user }: Auth) {
- return user.isAdmin;
-}
-
-export async function hasPermission(role: string, permission: string | string[]) {
- return ensureArray(permission).some(e => ROLE_PERMISSIONS[role]?.includes(e));
-}
diff --git a/src/lib/cache.ts b/src/lib/cache.ts
deleted file mode 100644
index 69d749d0..00000000
--- a/src/lib/cache.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { User, Website } from '@prisma/client';
-import redis from '@umami/redis-client';
-import { getSession, getUserById, getWebsiteById } from '../queries';
-
-async function fetchWebsite(id): Promise {
- return redis.client.getCache(`website:${id}`, () => getWebsiteById(id), 86400);
-}
-
-async function storeWebsite(data) {
- const { id } = data;
- const key = `website:${id}`;
-
- const obj = await redis.client.setCache(key, data);
- await redis.client.expire(key, 86400);
-
- return obj;
-}
-
-async function deleteWebsite(id) {
- return redis.client.deleteCache(`website:${id}`);
-}
-
-async function fetchUser(id): Promise {
- return redis.client.getCache(
- `user:${id}`,
- () => getUserById(id, { includePassword: true }),
- 86400,
- );
-}
-
-async function storeUser(data) {
- const { id } = data;
- const key = `user:${id}`;
-
- const obj = await redis.client.setCache(key, data);
- await redis.client.expire(key, 86400);
-
- return obj;
-}
-
-async function deleteUser(id) {
- return redis.client.deleteCache(`user:${id}`);
-}
-
-async function fetchSession(id) {
- return redis.client.getCache(`session:${id}`, () => getSession(id), 86400);
-}
-
-async function storeSession(data) {
- const { id } = data;
- const key = `session:${id}`;
-
- const obj = await redis.client.setCache(key, data);
- await redis.client.expire(key, 86400);
-
- return obj;
-}
-
-async function deleteSession(id) {
- return redis.client.deleteCache(`session:${id}`);
-}
-
-async function fetchUserBlock(userId: string) {
- const key = `user:block:${userId}`;
- return redis.client.get(key);
-}
-
-async function incrementUserBlock(userId: string) {
- const key = `user:block:${userId}`;
- return redis.client.incr(key);
-}
-
-export default {
- fetchWebsite,
- storeWebsite,
- deleteWebsite,
- fetchUser,
- storeUser,
- deleteUser,
- fetchSession,
- storeSession,
- deleteSession,
- fetchUserBlock,
- incrementUserBlock,
- enabled: !!redis.enabled,
-};
diff --git a/src/lib/charts.tsx b/src/lib/charts.tsx
deleted file mode 100644
index c80bfe3f..00000000
--- a/src/lib/charts.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { StatusLight } from 'react-basics';
-import { formatDate } from 'lib/date';
-import { formatLongNumber } from 'lib/format';
-
-export function renderNumberLabels(label: string) {
- return +label > 1000 ? formatLongNumber(+label) : label;
-}
-
-export function renderDateLabels(unit: string, locale: string) {
- return (label: string, index: number, values: any[]) => {
- const d = new Date(values[index].value);
-
- switch (unit) {
- case 'minute':
- return formatDate(d, 'h:mm', locale);
- case 'hour':
- return formatDate(d, 'p', locale);
- case 'day':
- return formatDate(d, 'MMM d', locale);
- case 'month':
- return formatDate(d, 'MMM', locale);
- case 'year':
- return formatDate(d, 'YYY', locale);
- default:
- return label;
- }
- };
-}
-
-export function renderStatusTooltipPopup(unit: string, locale: string) {
- return (setTooltipPopup: (data: any) => void, model: any) => {
- const { opacity, labelColors, dataPoints } = model.tooltip;
-
- if (!dataPoints?.length || !opacity) {
- setTooltipPopup(null);
- return;
- }
-
- const formats = {
- millisecond: 'T',
- second: 'pp',
- minute: 'p',
- hour: 'h:mm aaa - PP',
- day: 'PPPP',
- week: 'PPPP',
- month: 'LLLL yyyy',
- quarter: 'qqq',
- year: 'yyyy',
- };
-
- setTooltipPopup(
- <>
- {formatDate(new Date(dataPoints[0].raw.x), formats[unit], locale)}
-
-
- {formatLongNumber(dataPoints[0].raw.y)} {dataPoints[0].dataset.label}
-
-
- >,
- );
- };
-}
diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts
deleted file mode 100644
index 2eed340e..00000000
--- a/src/lib/clickhouse.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import { ClickHouseClient, createClient } from '@clickhouse/client';
-import dateFormat from 'dateformat';
-import debug from 'debug';
-import { CLICKHOUSE } from 'lib/db';
-import { QueryFilters, QueryOptions } from './types';
-import { FILTER_COLUMNS, OPERATORS } from './constants';
-import { loadWebsite } from './load';
-import { maxDate } from './date';
-
-export const CLICKHOUSE_DATE_FORMATS = {
- minute: '%Y-%m-%d %H:%M:00',
- hour: '%Y-%m-%d %H:00:00',
- day: '%Y-%m-%d',
- month: '%Y-%m-01',
- year: '%Y-01-01',
-};
-
-const log = debug('umami:clickhouse');
-
-let clickhouse: ClickHouseClient;
-const enabled = Boolean(process.env.CLICKHOUSE_URL);
-
-function getClient() {
- const {
- hostname,
- port,
- pathname,
- protocol,
- username = 'default',
- password,
- } = new URL(process.env.CLICKHOUSE_URL);
-
- const client = createClient({
- host: `${protocol}//${hostname}:${port}`,
- database: pathname.replace('/', ''),
- username: username,
- password,
- });
-
- if (process.env.NODE_ENV !== 'production') {
- global[CLICKHOUSE] = client;
- }
-
- log('Clickhouse initialized');
-
- return client;
-}
-
-function getDateStringQuery(data, unit) {
- return `formatDateTime(${data}, '${CLICKHOUSE_DATE_FORMATS[unit]}')`;
-}
-
-function getDateQuery(field, unit, timezone?) {
- if (timezone) {
- return `date_trunc('${unit}', ${field}, '${timezone}')`;
- }
- return `date_trunc('${unit}', ${field})`;
-}
-
-function getDateFormat(date) {
- return `'${dateFormat(date, 'UTC:yyyy-mm-dd HH:MM:ss')}'`;
-}
-
-function mapFilter(column, operator, name, type = 'String') {
- switch (operator) {
- case OPERATORS.equals:
- return `${column} = {${name}:${type}}`;
- case OPERATORS.notEquals:
- return `${column} != {${name}:${type}}`;
- default:
- return '';
- }
-}
-
-function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}) {
- const query = Object.keys(filters).reduce((arr, name) => {
- const value = filters[name];
- const operator = value?.filter ?? OPERATORS.equals;
- const column = FILTER_COLUMNS[name] ?? options?.columns?.[name];
-
- if (value !== undefined && column) {
- arr.push(`and ${mapFilter(column, operator, name)}`);
-
- if (name === 'referrer') {
- arr.push('and referrer_domain != {websiteDomain:String}');
- }
- }
-
- return arr;
- }, []);
-
- return query.join('\n');
-}
-
-function normalizeFilters(filters = {}) {
- return Object.keys(filters).reduce((obj, key) => {
- const value = filters[key];
-
- obj[key] = value?.value ?? value;
-
- return obj;
- }, {});
-}
-
-async function parseFilters(websiteId: string, filters: QueryFilters = {}, options?: QueryOptions) {
- const website = await loadWebsite(websiteId);
-
- return {
- filterQuery: getFilterQuery(filters, options),
- params: {
- ...normalizeFilters(filters),
- websiteId,
- startDate: maxDate(filters.startDate, new Date(website.resetAt)),
- websiteDomain: website.domain,
- },
- };
-}
-
-async function rawQuery(query: string, params: Record = {}): Promise {
- if (process.env.LOG_QUERY) {
- log('QUERY:\n', query);
- log('PARAMETERS:\n', params);
- }
-
- await connect();
-
- const resultSet = await clickhouse.query({
- query: query,
- query_params: params,
- format: 'JSONEachRow',
- });
-
- const data = await resultSet.json();
-
- return data;
-}
-
-async function findUnique(data) {
- if (data.length > 1) {
- throw `${data.length} records found when expecting 1.`;
- }
-
- return findFirst(data);
-}
-
-async function findFirst(data) {
- return data[0] ?? null;
-}
-
-async function connect() {
- if (enabled && !clickhouse) {
- clickhouse = process.env.CLICKHOUSE_URL && (global[CLICKHOUSE] || getClient());
- }
-
- return clickhouse;
-}
-
-export default {
- enabled,
- client: clickhouse,
- log,
- connect,
- getDateStringQuery,
- getDateQuery,
- getDateFormat,
- getFilterQuery,
- parseFilters,
- findUnique,
- findFirst,
- rawQuery,
-};
diff --git a/src/lib/client.ts b/src/lib/client.ts
deleted file mode 100644
index 7810c44a..00000000
--- a/src/lib/client.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { getItem, setItem, removeItem } from 'next-basics';
-import { AUTH_TOKEN } from './constants';
-
-export function getClientAuthToken() {
- return getItem(AUTH_TOKEN);
-}
-
-export function setClientAuthToken(token: string) {
- setItem(AUTH_TOKEN, token);
-}
-
-export function removeClientAuthToken() {
- removeItem(AUTH_TOKEN);
-}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
deleted file mode 100644
index 0c894634..00000000
--- a/src/lib/constants.ts
+++ /dev/null
@@ -1,521 +0,0 @@
-/* eslint-disable no-unused-vars */
-export const CURRENT_VERSION = process.env.currentVersion;
-export const AUTH_TOKEN = 'umami.auth';
-export const LOCALE_CONFIG = 'umami.locale';
-export const TIMEZONE_CONFIG = 'umami.timezone';
-export const DATE_RANGE_CONFIG = 'umami.date-range';
-export const THEME_CONFIG = 'umami.theme';
-export const DASHBOARD_CONFIG = 'umami.dashboard';
-export const VERSION_CHECK = 'umami.version-check';
-export const SHARE_TOKEN_HEADER = 'x-umami-share-token';
-export const HOMEPAGE_URL = 'https://umami.is';
-export const REPO_URL = 'https://github.com/umami-software/umami';
-export const UPDATES_URL = 'https://api.umami.is/v1/updates';
-export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png';
-
-export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US';
-export const DEFAULT_THEME = 'light';
-export const DEFAULT_ANIMATION_DURATION = 300;
-export const DEFAULT_DATE_RANGE = '24hour';
-export const DEFAULT_WEBSITE_LIMIT = 10;
-export const DEFAULT_RESET_DATE = '2000-01-01';
-export const DEFAULT_PAGE_SIZE = 10;
-
-export const REALTIME_RANGE = 30;
-export const REALTIME_INTERVAL = 5000;
-
-export const FILTER_COMBINED = 'filter-combined';
-export const FILTER_RAW = 'filter-raw';
-export const FILTER_DAY = 'filter-day';
-export const FILTER_RANGE = 'filter-range';
-export const FILTER_REFERRERS = 'filter-referrers';
-export const FILTER_PAGES = 'filter-pages';
-export const UNIT_TYPES = ['year', 'month', 'hour', 'day'];
-export const EVENT_COLUMNS = ['url', 'referrer', 'title', 'query', 'event'];
-
-export const SESSION_COLUMNS = [
- 'browser',
- 'os',
- 'device',
- 'screen',
- 'language',
- 'country',
- 'region',
- 'city',
-];
-
-export const FILTER_COLUMNS = {
- url: 'url_path',
- referrer: 'referrer_domain',
- title: 'page_title',
- query: 'url_query',
- os: 'os',
- browser: 'browser',
- device: 'device',
- country: 'country',
- region: 'subdivision1',
- city: 'city',
- language: 'language',
- event: 'event_name',
-};
-
-export const COLLECTION_TYPE = {
- event: 'event',
- identify: 'identify',
-};
-
-export const EVENT_TYPE = {
- pageView: 1,
- customEvent: 2,
-} as const;
-
-export const DATA_TYPE = {
- string: 1,
- number: 2,
- boolean: 3,
- date: 4,
- array: 5,
-} as const;
-
-export const OPERATORS = {
- equals: 'eq',
- notEquals: 'neq',
- set: 's',
- notSet: 'ns',
- contains: 'c',
- doesNotContain: 'dnc',
- true: 't',
- false: 'f',
- greaterThan: 'gt',
- lessThan: 'lt',
- greaterThanEquals: 'gte',
- lessThanEquals: 'lte',
- before: 'bf',
- after: 'af',
-} as const;
-
-export const DATA_TYPES = {
- [DATA_TYPE.string]: 'string',
- [DATA_TYPE.number]: 'number',
- [DATA_TYPE.boolean]: 'boolean',
- [DATA_TYPE.date]: 'date',
- [DATA_TYPE.array]: 'array',
-};
-
-export const REPORT_TYPES = {
- funnel: 'funnel',
- insights: 'insights',
- retention: 'retention',
-} as const;
-
-export const REPORT_PARAMETERS = {
- fields: 'fields',
- filters: 'filters',
- groups: 'groups',
-} as const;
-
-export const KAFKA_TOPIC = {
- event: 'event',
- eventData: 'event_data',
-} as const;
-
-export const ROLES = {
- admin: 'admin',
- user: 'user',
- viewOnly: 'view-only',
- teamOwner: 'team-owner',
- teamMember: 'team-member',
-} as const;
-
-export const PERMISSIONS = {
- all: 'all',
- websiteCreate: 'website:create',
- websiteUpdate: 'website:update',
- websiteDelete: 'website:delete',
- teamCreate: 'team:create',
- teamUpdate: 'team:update',
- teamDelete: 'team:delete',
-} as const;
-
-export const ROLE_PERMISSIONS = {
- [ROLES.admin]: [PERMISSIONS.all],
- [ROLES.user]: [
- PERMISSIONS.websiteCreate,
- PERMISSIONS.websiteUpdate,
- PERMISSIONS.websiteDelete,
- PERMISSIONS.teamCreate,
- ],
- [ROLES.viewOnly]: [],
- [ROLES.teamOwner]: [PERMISSIONS.teamUpdate, PERMISSIONS.teamDelete],
- [ROLES.teamMember]: [],
-} as const;
-
-export const THEME_COLORS = {
- light: {
- primary: '#2680eb',
- gray50: '#ffffff',
- gray75: '#fafafa',
- gray100: '#f5f5f5',
- gray200: '#eaeaea',
- gray300: '#e1e1e1',
- gray400: '#cacaca',
- gray500: '#b3b3b3',
- gray600: '#8e8e8e',
- gray700: '#6e6e6e',
- gray800: '#4b4b4b',
- gray900: '#2c2c2c',
- },
- dark: {
- primary: '#2680eb',
- gray50: '#252525',
- gray75: '#2f2f2f',
- gray100: '#323232',
- gray200: '#3e3e3e',
- gray300: '#4a4a4a',
- gray400: '#5a5a5a',
- gray500: '#6e6e6e',
- gray600: '#909090',
- gray700: '#b9b9b9',
- gray800: '#e3e3e3',
- gray900: '#ffffff',
- },
-};
-
-export const EVENT_COLORS = [
- '#2680eb',
- '#9256d9',
- '#44b556',
- '#e68619',
- '#e34850',
- '#f7bd12',
- '#01bad7',
- '#6734bc',
- '#89c541',
- '#ffc301',
- '#ec1562',
- '#ffec16',
-];
-
-export const DOMAIN_REGEX =
- /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9-]+(-[a-z0-9-]+)*\.)+(xn--)?[a-z0-9-]{2,63})$/;
-export const SHARE_ID_REGEX = /^[a-zA-Z0-9]{8,16}$/;
-export const UUID_REGEX =
- /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
-export const HOSTNAME_REGEX =
- /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
-
-export const DESKTOP_SCREEN_WIDTH = 1920;
-export const LAPTOP_SCREEN_WIDTH = 1024;
-export const MOBILE_SCREEN_WIDTH = 479;
-
-export const URL_LENGTH = 500;
-export const EVENT_NAME_LENGTH = 50;
-
-export const DESKTOP_OS = [
- 'BeOS',
- 'Chrome OS',
- 'Linux',
- 'Mac OS',
- 'Open BSD',
- 'OS/2',
- 'QNX',
- 'Sun OS',
- 'Windows 10',
- 'Windows 2000',
- 'Windows 3.11',
- 'Windows 7',
- 'Windows 8',
- 'Windows 8.1',
- 'Windows 95',
- 'Windows 98',
- 'Windows ME',
- 'Windows Server 2003',
- 'Windows Vista',
- 'Windows XP',
-];
-
-export const MOBILE_OS = ['Amazon OS', 'Android OS', 'BlackBerry OS', 'iOS', 'Windows Mobile'];
-
-export const BROWSERS = {
- android: 'Android',
- aol: 'AOL',
- beaker: 'Beaker',
- bb10: 'BlackBerry 10',
- chrome: 'Chrome',
- 'chromium-webview': 'Chrome (webview)',
- crios: 'Chrome (iOS)',
- curl: 'Curl',
- edge: 'Edge',
- 'edge-chromium': 'Edge (Chromium)',
- 'edge-ios': 'Edge (iOS)',
- facebook: 'Facebook',
- firefox: 'Firefox',
- fxios: 'Firefox (iOS)',
- ie: 'IE',
- instagram: 'Instagram',
- ios: 'iOS',
- 'ios-webview': 'iOS (webview)',
- kakaotalk: 'KaKaoTalk',
- miui: 'MIUI',
- opera: 'Opera',
- 'opera-mini': 'Opera Mini',
- phantomjs: 'PhantomJS',
- safari: 'Safari',
- samsung: 'Samsung',
- silk: 'Silk',
- searchbot: 'Searchbot',
- yandexbrowser: 'Yandex',
-};
-
-export const MAP_FILE = '/datamaps.world.json';
-
-export const ISO_COUNTRIES = {
- AFG: 'AF',
- ALA: 'AX',
- ALB: 'AL',
- DZA: 'DZ',
- ASM: 'AS',
- AND: 'AD',
- AGO: 'AO',
- AIA: 'AI',
- ATA: 'AQ',
- ATG: 'AG',
- ARG: 'AR',
- ARM: 'AM',
- ABW: 'AW',
- AUS: 'AU',
- AUT: 'AT',
- AZE: 'AZ',
- BHS: 'BS',
- BHR: 'BH',
- BGD: 'BD',
- BRB: 'BB',
- BLR: 'BY',
- BEL: 'BE',
- BLZ: 'BZ',
- BEN: 'BJ',
- BMU: 'BM',
- BTN: 'BT',
- BOL: 'BO',
- BIH: 'BA',
- BWA: 'BW',
- BVT: 'BV',
- BRA: 'BR',
- VGB: 'VG',
- IOT: 'IO',
- BRN: 'BN',
- BGR: 'BG',
- BFA: 'BF',
- BDI: 'BI',
- KHM: 'KH',
- CMR: 'CM',
- CAN: 'CA',
- CPV: 'CV',
- CYM: 'KY',
- CAF: 'CF',
- TCD: 'TD',
- CHL: 'CL',
- CHN: 'CN',
- HKG: 'HK',
- MAC: 'MO',
- CXR: 'CX',
- CCK: 'CC',
- COL: 'CO',
- COM: 'KM',
- COG: 'CG',
- COD: 'CD',
- COK: 'CK',
- CRI: 'CR',
- CIV: 'CI',
- HRV: 'HR',
- CUB: 'CU',
- CYP: 'CY',
- CZE: 'CZ',
- DNK: 'DK',
- DJI: 'DJ',
- DMA: 'DM',
- DOM: 'DO',
- ECU: 'EC',
- EGY: 'EG',
- SLV: 'SV',
- GNQ: 'GQ',
- ERI: 'ER',
- EST: 'EE',
- ETH: 'ET',
- FLK: 'FK',
- FRO: 'FO',
- FJI: 'FJ',
- FIN: 'FI',
- FRA: 'FR',
- GUF: 'GF',
- PYF: 'PF',
- ATF: 'TF',
- GAB: 'GA',
- GMB: 'GM',
- GEO: 'GE',
- DEU: 'DE',
- GHA: 'GH',
- GIB: 'GI',
- GRC: 'GR',
- GRL: 'GL',
- GRD: 'GD',
- GLP: 'GP',
- GUM: 'GU',
- GTM: 'GT',
- GGY: 'GG',
- GIN: 'GN',
- GNB: 'GW',
- GUY: 'GY',
- HTI: 'HT',
- HMD: 'HM',
- VAT: 'VA',
- HND: 'HN',
- HUN: 'HU',
- ISL: 'IS',
- IND: 'IN',
- IDN: 'ID',
- IRN: 'IR',
- IRQ: 'IQ',
- IRL: 'IE',
- IMN: 'IM',
- ISR: 'IL',
- ITA: 'IT',
- JAM: 'JM',
- JPN: 'JP',
- JEY: 'JE',
- JOR: 'JO',
- KAZ: 'KZ',
- KEN: 'KE',
- KIR: 'KI',
- PRK: 'KP',
- KOR: 'KR',
- KWT: 'KW',
- KGZ: 'KG',
- LAO: 'LA',
- LVA: 'LV',
- LBN: 'LB',
- LSO: 'LS',
- LBR: 'LR',
- LBY: 'LY',
- LIE: 'LI',
- LTU: 'LT',
- LUX: 'LU',
- MKD: 'MK',
- MDG: 'MG',
- MWI: 'MW',
- MYS: 'MY',
- MDV: 'MV',
- MLI: 'ML',
- MLT: 'MT',
- MHL: 'MH',
- MTQ: 'MQ',
- MRT: 'MR',
- MUS: 'MU',
- MYT: 'YT',
- MEX: 'MX',
- FSM: 'FM',
- MDA: 'MD',
- MCO: 'MC',
- MNG: 'MN',
- MNE: 'ME',
- MSR: 'MS',
- MAR: 'MA',
- MOZ: 'MZ',
- MMR: 'MM',
- NAM: 'NA',
- NRU: 'NR',
- NPL: 'NP',
- NLD: 'NL',
- ANT: 'AN',
- NCL: 'NC',
- NZL: 'NZ',
- NIC: 'NI',
- NER: 'NE',
- NGA: 'NG',
- NIU: 'NU',
- NFK: 'NF',
- MNP: 'MP',
- NOR: 'NO',
- OMN: 'OM',
- PAK: 'PK',
- PLW: 'PW',
- PSE: 'PS',
- PAN: 'PA',
- PNG: 'PG',
- PRY: 'PY',
- PER: 'PE',
- PHL: 'PH',
- PCN: 'PN',
- POL: 'PL',
- PRT: 'PT',
- PRI: 'PR',
- QAT: 'QA',
- REU: 'RE',
- ROU: 'RO',
- RUS: 'RU',
- RWA: 'RW',
- BLM: 'BL',
- SHN: 'SH',
- KNA: 'KN',
- LCA: 'LC',
- MAF: 'MF',
- SPM: 'PM',
- VCT: 'VC',
- WSM: 'WS',
- SMR: 'SM',
- STP: 'ST',
- SAU: 'SA',
- SEN: 'SN',
- SRB: 'RS',
- SYC: 'SC',
- SLE: 'SL',
- SGP: 'SG',
- SVK: 'SK',
- SVN: 'SI',
- SLB: 'SB',
- SOM: 'SO',
- ZAF: 'ZA',
- SGS: 'GS',
- SSD: 'SS',
- ESP: 'ES',
- LKA: 'LK',
- SDN: 'SD',
- SUR: 'SR',
- SJM: 'SJ',
- SWZ: 'SZ',
- SWE: 'SE',
- CHE: 'CH',
- SYR: 'SY',
- TWN: 'TW',
- TJK: 'TJ',
- TZA: 'TZ',
- THA: 'TH',
- TLS: 'TL',
- TGO: 'TG',
- TKL: 'TK',
- TON: 'TO',
- TTO: 'TT',
- TUN: 'TN',
- TUR: 'TR',
- TKM: 'TM',
- TCA: 'TC',
- TUV: 'TV',
- UGA: 'UG',
- UKR: 'UA',
- ARE: 'AE',
- GBR: 'GB',
- USA: 'US',
- UMI: 'UM',
- URY: 'UY',
- UZB: 'UZ',
- VUT: 'VU',
- VEN: 'VE',
- VNM: 'VN',
- VIR: 'VI',
- WLF: 'WF',
- ESH: 'EH',
- YEM: 'YE',
- ZMB: 'ZM',
- ZWE: 'ZW',
- XKX: 'XK',
-};
diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts
deleted file mode 100644
index a2763352..00000000
--- a/src/lib/crypto.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { startOfMonth } from 'date-fns';
-import { hash } from 'next-basics';
-import { v4, v5, validate } from 'uuid';
-
-export function secret() {
- return hash(process.env.APP_SECRET || process.env.DATABASE_URL);
-}
-
-export function salt() {
- const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString());
-
- return hash(secret(), ROTATING_SALT);
-}
-
-export function uuid(...args: any) {
- if (!args.length) return v4();
-
- return v5(hash(...args, salt()), v5.DNS);
-}
-
-export function isUuid(value: string) {
- return validate(value);
-}
diff --git a/src/lib/data.ts b/src/lib/data.ts
deleted file mode 100644
index 47023bb4..00000000
--- a/src/lib/data.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { isValid, parseISO } from 'date-fns';
-import { DATA_TYPE } from './constants';
-import { DynamicDataType } from './types';
-
-export function flattenJSON(
- eventData: { [key: string]: any },
- keyValues: { key: string; value: any; dynamicDataType: DynamicDataType }[] = [],
- parentKey = '',
-): { key: string; value: any; dynamicDataType: DynamicDataType }[] {
- return Object.keys(eventData).reduce(
- (acc, key) => {
- const value = eventData[key];
- const type = typeof eventData[key];
-
- // nested object
- if (value && type === 'object' && !Array.isArray(value) && !isValid(value)) {
- flattenJSON(value, acc.keyValues, getKeyName(key, parentKey));
- } else {
- createKey(getKeyName(key, parentKey), value, acc);
- }
-
- return acc;
- },
- { keyValues, parentKey },
- ).keyValues;
-}
-
-export function getDataType(value: any): string {
- let type: string = typeof value;
-
- if ((type === 'string' && isValid(value)) || isValid(parseISO(value))) {
- type = 'date';
- }
-
- return type;
-}
-
-function createKey(key, value, acc: { keyValues: any[]; parentKey: string }) {
- const type = getDataType(value);
-
- let dynamicDataType = null;
-
- switch (type) {
- case 'number':
- dynamicDataType = DATA_TYPE.number;
- break;
- case 'string':
- dynamicDataType = DATA_TYPE.string;
- break;
- case 'boolean':
- dynamicDataType = DATA_TYPE.boolean;
- value = value ? 'true' : 'false';
- break;
- case 'date':
- dynamicDataType = DATA_TYPE.date;
- break;
- case 'object':
- dynamicDataType = DATA_TYPE.array;
- value = JSON.stringify(value);
- break;
- default:
- dynamicDataType = DATA_TYPE.string;
- break;
- }
-
- acc.keyValues.push({ key, value, dynamicDataType });
-}
-
-function getKeyName(key, parentKey) {
- if (!parentKey) {
- return key;
- }
-
- return `${parentKey}.${key}`;
-}
diff --git a/src/lib/date.ts b/src/lib/date.ts
deleted file mode 100644
index 81c37e69..00000000
--- a/src/lib/date.ts
+++ /dev/null
@@ -1,333 +0,0 @@
-import moment from 'moment-timezone';
-import {
- addMinutes,
- addHours,
- addDays,
- addMonths,
- addYears,
- subHours,
- subDays,
- subMonths,
- subYears,
- startOfMinute,
- startOfHour,
- startOfDay,
- startOfWeek,
- startOfMonth,
- startOfYear,
- endOfHour,
- endOfDay,
- endOfWeek,
- endOfMonth,
- endOfYear,
- differenceInMinutes,
- differenceInHours,
- differenceInCalendarDays,
- differenceInCalendarMonths,
- differenceInCalendarYears,
- format,
- max,
- min,
- isDate,
- subWeeks,
-} from 'date-fns';
-import { getDateLocale } from 'lib/lang';
-import { DateRange } from 'lib/types';
-
-export const TIME_UNIT = {
- minute: 'minute',
- hour: 'hour',
- day: 'day',
- week: 'week',
- month: 'month',
- year: 'year',
-};
-
-const dateFuncs = {
- minute: [differenceInMinutes, addMinutes, startOfMinute],
- hour: [differenceInHours, addHours, startOfHour],
- day: [differenceInCalendarDays, addDays, startOfDay],
- month: [differenceInCalendarMonths, addMonths, startOfMonth],
- year: [differenceInCalendarYears, addYears, startOfYear],
-};
-
-export function getTimezone() {
- return moment.tz.guess();
-}
-
-export function getLocalTime(t: string | number | Date) {
- return addMinutes(new Date(t), new Date().getTimezoneOffset());
-}
-
-export function parseDateRange(value: string | object, locale = 'en-US'): DateRange {
- if (typeof value === 'object') {
- return value as DateRange;
- }
-
- if (value === 'all') {
- return {
- startDate: new Date(0),
- endDate: new Date(1),
- value,
- };
- }
-
- if (value?.startsWith?.('range')) {
- const [, startTime, endTime] = value.split(':');
-
- const startDate = new Date(+startTime);
- const endDate = new Date(+endTime);
-
- return {
- startDate,
- endDate,
- unit: getMinimumUnit(startDate, endDate),
- value,
- };
- }
-
- const now = new Date();
- const dateLocale = getDateLocale(locale);
-
- const match = value?.match?.(/^(?[0-9-]+)(?hour|day|week|month|year)$/);
-
- if (!match) return null;
-
- const { num, unit } = match.groups;
- const selectedUnit = { num: +num, unit };
-
- if (+num === 1) {
- switch (unit) {
- case 'day':
- return {
- startDate: startOfDay(now),
- endDate: endOfDay(now),
- unit: 'hour',
- value,
- selectedUnit,
- };
- case 'week':
- return {
- startDate: startOfWeek(now, { locale: dateLocale }),
- endDate: endOfWeek(now, { locale: dateLocale }),
- unit: 'day',
- value,
- selectedUnit,
- };
- case 'month':
- return {
- startDate: startOfMonth(now),
- endDate: endOfMonth(now),
- unit: 'day',
- value,
- selectedUnit,
- };
- case 'year':
- return {
- startDate: startOfYear(now),
- endDate: endOfYear(now),
- unit: 'month',
- value,
- selectedUnit,
- };
- }
- }
-
- if (+num === -1) {
- switch (unit) {
- case 'day':
- return {
- startDate: subDays(startOfDay(now), 1),
- endDate: subDays(endOfDay(now), 1),
- unit: 'hour',
- value,
- selectedUnit,
- };
- case 'week':
- return {
- startDate: subDays(startOfWeek(now, { locale: dateLocale }), 7),
- endDate: subDays(endOfWeek(now, { locale: dateLocale }), 1),
- unit: 'day',
- value,
- selectedUnit,
- };
- case 'month':
- return {
- startDate: subMonths(startOfMonth(now), 1),
- endDate: subMonths(endOfMonth(now), 1),
- unit: 'day',
- value,
- selectedUnit,
- };
- case 'year':
- return {
- startDate: subYears(startOfYear(now), 1),
- endDate: subYears(endOfYear(now), 1),
- unit: 'month',
- value,
- selectedUnit,
- };
- }
- }
-
- switch (unit) {
- case 'day':
- return {
- startDate: subDays(startOfDay(now), +num - 1),
- endDate: endOfDay(now),
- unit,
- value,
- selectedUnit,
- };
- case 'hour':
- return {
- startDate: subHours(startOfHour(now), +num - 1),
- endDate: endOfHour(now),
- unit,
- value,
- selectedUnit,
- };
- }
-}
-
-export function incrementDateRange(
- value: { startDate: any; endDate: any; selectedUnit: any },
- increment: number,
-) {
- const { startDate, endDate, selectedUnit } = value;
-
- const { num, unit } = selectedUnit;
-
- const sub = Math.abs(num) * increment;
-
- switch (unit) {
- case 'hour':
- return {
- ...value,
- startDate: subHours(startDate, sub),
- endDate: subHours(endDate, sub),
- value: 'range',
- };
- case 'day':
- return {
- ...value,
- startDate: subDays(startDate, sub),
- endDate: subDays(endDate, sub),
- value: 'range',
- };
- case 'week':
- return {
- ...value,
- startDate: subWeeks(startDate, sub),
- endDate: subWeeks(endDate, sub),
- value: 'range',
- };
- case 'month':
- return {
- ...value,
- startDate: subMonths(startDate, sub),
- endDate: subMonths(endDate, sub),
- value: 'range',
- };
- case 'year':
- return {
- ...value,
- startDate: subYears(startDate, sub),
- endDate: subYears(endDate, sub),
- value: 'range',
- };
- }
-}
-
-export function getAllowedUnits(startDate: Date, endDate: Date) {
- const units = ['minute', 'hour', 'day', 'month', 'year'];
- const minUnit = getMinimumUnit(startDate, endDate);
- const index = units.indexOf(minUnit === 'year' ? 'month' : minUnit);
-
- return index >= 0 ? units.splice(index) : [];
-}
-
-export function getMinimumUnit(startDate: number | Date, endDate: number | Date) {
- if (differenceInMinutes(endDate, startDate) <= 60) {
- return 'minute';
- } else if (differenceInHours(endDate, startDate) <= 48) {
- return 'hour';
- } else if (differenceInCalendarMonths(endDate, startDate) <= 12) {
- return 'day';
- } else if (differenceInCalendarMonths(endDate, startDate) <= 24) {
- return 'month';
- }
-
- return 'year';
-}
-
-export function getDateFromString(str: string) {
- const [ymd, hms] = str.split(' ');
- const [year, month, day] = ymd.split('-');
-
- if (hms) {
- const [hour, min, sec] = hms.split(':');
-
- return new Date(+year, +month - 1, +day, +hour, +min, +sec);
- }
-
- return new Date(+year, +month - 1, +day);
-}
-
-export function getDateArray(data: any[], startDate: Date, endDate: Date, unit: string) {
- const arr = [];
- const [diff, add, normalize] = dateFuncs[unit];
- const n = diff(endDate, startDate) + 1;
-
- function findData(date: Date) {
- const d = data.find(({ x }) => {
- return normalize(getDateFromString(x)).getTime() === date.getTime();
- });
-
- return d?.y || 0;
- }
-
- for (let i = 0; i < n; i++) {
- const t = normalize(add(startDate, i));
- const y = findData(t);
-
- arr.push({ x: t, y });
- }
-
- return arr;
-}
-
-export function getDateLength(startDate: Date, endDate: Date, unit: string | number) {
- const [diff] = dateFuncs[unit];
- return diff(endDate, startDate) + 1;
-}
-
-export const CUSTOM_FORMATS = {
- 'en-US': {
- p: 'ha',
- pp: 'h:mm:ss',
- },
- 'fr-FR': {
- 'M/d': 'd/M',
- 'MMM d': 'd MMM',
- 'EEE M/d': 'EEE d/M',
- },
-};
-
-export function formatDate(date: string | number | Date, str: string, locale = 'en-US') {
- return format(
- typeof date === 'string' ? new Date(date) : date,
- CUSTOM_FORMATS?.[locale]?.[str] || str,
- {
- locale: getDateLocale(locale),
- },
- );
-}
-
-export function maxDate(...args: Date[]) {
- return max(args.filter(n => isDate(n)));
-}
-
-export function minDate(...args: any[]) {
- return min(args.filter(n => isDate(n)));
-}
diff --git a/src/lib/db.ts b/src/lib/db.ts
deleted file mode 100644
index 46f73aa2..00000000
--- a/src/lib/db.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-export const PRISMA = 'prisma';
-export const POSTGRESQL = 'postgresql';
-export const MYSQL = 'mysql';
-export const CLICKHOUSE = 'clickhouse';
-export const KAFKA = 'kafka';
-export const KAFKA_PRODUCER = 'kafka-producer';
-
-// Fixes issue with converting bigint values
-BigInt.prototype['toJSON'] = function () {
- return Number(this);
-};
-
-export function getDatabaseType(url = process.env.DATABASE_URL) {
- const type = url && url.split(':')[0];
-
- if (type === 'postgres') {
- return POSTGRESQL;
- }
-
- if (process.env.CLICKHOUSE_URL) {
- return CLICKHOUSE;
- }
-
- return type;
-}
-
-export async function runQuery(queries: any) {
- const db = getDatabaseType(process.env.CLICKHOUSE_URL || process.env.DATABASE_URL);
-
- if (db === POSTGRESQL || db === MYSQL) {
- return queries[PRISMA]();
- }
-
- if (db === CLICKHOUSE) {
- if (queries[KAFKA]) {
- return queries[KAFKA]();
- }
-
- return queries[CLICKHOUSE]();
- }
-}
-
-export function notImplemented() {
- throw new Error('Not implemented.');
-}
diff --git a/src/lib/detect.ts b/src/lib/detect.ts
deleted file mode 100644
index dab08312..00000000
--- a/src/lib/detect.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import path from 'path';
-import { getClientIp } from 'request-ip';
-import { browserName, detectOS } from 'detect-browser';
-import isLocalhost from 'is-localhost-ip';
-import maxmind from 'maxmind';
-import { safeDecodeURIComponent } from 'next-basics';
-
-import {
- DESKTOP_OS,
- MOBILE_OS,
- DESKTOP_SCREEN_WIDTH,
- LAPTOP_SCREEN_WIDTH,
- MOBILE_SCREEN_WIDTH,
-} from './constants';
-import { NextApiRequestCollect } from 'pages/api/send';
-
-let lookup;
-
-export function getIpAddress(req) {
- // Custom header
- if (req.headers[process.env.CLIENT_IP_HEADER]) {
- return req.headers[process.env.CLIENT_IP_HEADER];
- }
- // Cloudflare
- else if (req.headers['cf-connecting-ip']) {
- return req.headers['cf-connecting-ip'];
- }
-
- return getClientIp(req);
-}
-
-export function getDevice(screen, os) {
- if (!screen) return;
-
- const [width] = screen.split('x');
-
- if (DESKTOP_OS.includes(os)) {
- if (os === 'Chrome OS' || width < DESKTOP_SCREEN_WIDTH) {
- return 'laptop';
- }
- return 'desktop';
- } else if (MOBILE_OS.includes(os)) {
- if (os === 'Amazon OS' || width > MOBILE_SCREEN_WIDTH) {
- return 'tablet';
- }
- return 'mobile';
- }
-
- if (width >= DESKTOP_SCREEN_WIDTH) {
- return 'desktop';
- } else if (width >= LAPTOP_SCREEN_WIDTH) {
- return 'laptop';
- } else if (width >= MOBILE_SCREEN_WIDTH) {
- return 'tablet';
- } else {
- return 'mobile';
- }
-}
-
-function getRegionCode(country, region) {
- if (!country || !region) {
- return undefined;
- }
-
- return region.includes('-') ? region : `${country}-${region}`;
-}
-
-export async function getLocation(ip, req) {
- // Ignore local ips
- if (await isLocalhost(ip)) {
- return;
- }
-
- // Cloudflare headers
- if (req.headers['cf-ipcountry']) {
- const country = safeDecodeURIComponent(req.headers['cf-ipcountry']);
- const subdivision1 = safeDecodeURIComponent(req.headers['cf-region-code']);
- const city = safeDecodeURIComponent(req.headers['cf-ipcity']);
-
- return {
- country,
- subdivision1: getRegionCode(country, subdivision1),
- city,
- };
- }
-
- // Vercel headers
- if (req.headers['x-vercel-ip-country']) {
- const country = safeDecodeURIComponent(req.headers['x-vercel-ip-country']);
- const subdivision1 = safeDecodeURIComponent(req.headers['x-vercel-ip-country-region']);
- const city = safeDecodeURIComponent(req.headers['x-vercel-ip-city']);
-
- return {
- country,
- subdivision1: getRegionCode(country, subdivision1),
- city,
- };
- }
-
- // Database lookup
- if (!lookup) {
- const dir = path.join(process.cwd(), 'geo');
-
- lookup = await maxmind.open(path.resolve(dir, 'GeoLite2-City.mmdb'));
- }
-
- const result = lookup.get(ip);
-
- if (result) {
- const country = result.country?.iso_code ?? result?.registered_country?.iso_code;
- const subdivision1 = result.subdivisions?.[0]?.iso_code;
- const subdivision2 = result.subdivisions?.[1]?.names?.en;
- const city = result.city?.names?.en;
-
- return {
- country,
- subdivision1: getRegionCode(country, subdivision1),
- subdivision2,
- city,
- };
- }
-}
-
-export async function getClientInfo(req: NextApiRequestCollect, { screen }) {
- const userAgent = req.headers['user-agent'];
- const ip = getIpAddress(req);
- const location = await getLocation(ip, req);
- const country = location?.country;
- const subdivision1 = location?.subdivision1;
- const subdivision2 = location?.subdivision2;
- const city = location?.city;
- const browser = browserName(userAgent);
- const os = detectOS(userAgent);
- const device = getDevice(screen, os);
-
- return { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device };
-}
diff --git a/src/lib/filters.ts b/src/lib/filters.ts
deleted file mode 100644
index 1b4a5a1e..00000000
--- a/src/lib/filters.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-export const urlFilter = (data: any[]) => {
- const map = data.reduce((obj, { x, y }) => {
- if (x) {
- if (!obj[x]) {
- obj[x] = y;
- } else {
- obj[x] += y;
- }
- }
-
- return obj;
- }, {});
-
- return Object.keys(map).map(key => ({ x: key, y: map[key] }));
-};
-
-export const refFilter = (data: any[]) => {
- const links = {};
-
- const map = data.reduce((obj, { x, y }) => {
- let id;
-
- try {
- const url = new URL(x);
-
- id = url.hostname.replace(/www\./, '') || url.href;
- } catch {
- id = '';
- }
-
- links[id] = x;
-
- if (!obj[id]) {
- obj[id] = y;
- } else {
- obj[id] += y;
- }
-
- return obj;
- }, {});
-
- return Object.keys(map).map(key => ({ x: key, y: map[key], w: links[key] }));
-};
-
-export const emptyFilter = (data: any[]) => {
- return data.map(item => (item.x ? item : null)).filter(n => n);
-};
-
-export const percentFilter = (data: any[]) => {
- const total = data.reduce((n, { y }) => n + y, 0);
- return data.map(({ x, y, ...props }) => ({ x, y, z: total ? (y / total) * 100 : 0, ...props }));
-};
-
-export const paramFilter = (data: any[]) => {
- const map = data.reduce((obj, { x, y }) => {
- try {
- const searchParams = new URLSearchParams(x);
-
- for (const [key, value] of searchParams) {
- if (!obj[key]) {
- obj[key] = { [value]: y };
- } else if (!obj[key][value]) {
- obj[key][value] = y;
- } else {
- obj[key][value] += y;
- }
- }
- } catch {
- // Ignore
- }
-
- return obj;
- }, {});
-
- return Object.keys(map).flatMap(key =>
- Object.keys(map[key]).map(n => ({ x: `${key}=${n}`, p: key, v: n, y: map[key][n] })),
- );
-};
diff --git a/src/lib/format.ts b/src/lib/format.ts
deleted file mode 100644
index a662a9eb..00000000
--- a/src/lib/format.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-export function parseTime(val: number) {
- const days = ~~(val / 86400);
- const hours = ~~(val / 3600) - days * 24;
- const minutes = ~~(val / 60) - days * 1440 - hours * 60;
- const seconds = ~~val - days * 86400 - hours * 3600 - minutes * 60;
- const ms = (val - ~~val) * 1000;
-
- return {
- days,
- hours,
- minutes,
- seconds,
- ms,
- };
-}
-
-export function formatTime(val: number) {
- const { hours, minutes, seconds } = parseTime(val);
- const h = hours > 0 ? `${hours}:` : '';
- const m = hours > 0 ? minutes.toString().padStart(2, '0') : minutes;
- const s = seconds.toString().padStart(2, '0');
-
- return `${h}${m}:${s}`;
-}
-
-export function formatShortTime(val: number, formats = ['m', 's'], space = '') {
- const { days, hours, minutes, seconds, ms } = parseTime(val);
- let t = '';
-
- if (days > 0 && formats.indexOf('d') !== -1) t += `${days}d${space}`;
- if (hours > 0 && formats.indexOf('h') !== -1) t += `${hours}h${space}`;
- if (minutes > 0 && formats.indexOf('m') !== -1) t += `${minutes}m${space}`;
- if (seconds > 0 && formats.indexOf('s') !== -1) t += `${seconds}s${space}`;
- if (ms > 0 && formats.indexOf('ms') !== -1) t += `${ms}ms`;
-
- if (!t) {
- return `0${formats[formats.length - 1]}`;
- }
-
- return t;
-}
-
-export function formatNumber(n: string | number) {
- return Number(n).toFixed(0);
-}
-
-export function formatLongNumber(value: number) {
- const n = Number(value);
-
- if (n >= 1000000) {
- return `${(n / 1000000).toFixed(1)}m`;
- }
- if (n >= 100000) {
- return `${(n / 1000).toFixed(0)}k`;
- }
- if (n >= 10000) {
- return `${(n / 1000).toFixed(1)}k`;
- }
- if (n >= 1000) {
- return `${(n / 1000).toFixed(2)}k`;
- }
-
- return formatNumber(n);
-}
-
-export function stringToColor(str: string) {
- if (!str) {
- return '#ffffff';
- }
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
- let color = '#';
- for (let i = 0; i < 3; i++) {
- const value = (hash >> (i * 8)) & 0xff;
- color += ('00' + value.toString(16)).slice(-2);
- }
- return color;
-}
diff --git a/src/lib/kafka.ts b/src/lib/kafka.ts
deleted file mode 100644
index 10326888..00000000
--- a/src/lib/kafka.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import dateFormat from 'dateformat';
-import debug from 'debug';
-import { Kafka, Mechanism, Producer, RecordMetadata, SASLOptions, logLevel } from 'kafkajs';
-import { KAFKA, KAFKA_PRODUCER } from 'lib/db';
-import * as tls from 'tls';
-
-const log = debug('umami:kafka');
-
-let kafka: Kafka;
-let producer: Producer;
-const enabled = Boolean(process.env.KAFKA_URL && process.env.KAFKA_BROKER);
-
-function getClient() {
- const { username, password } = new URL(process.env.KAFKA_URL);
- const brokers = process.env.KAFKA_BROKER.split(',');
-
- const ssl: { ssl?: tls.ConnectionOptions | boolean; sasl?: SASLOptions | Mechanism } =
- username && password
- ? {
- ssl: {
- checkServerIdentity: () => undefined,
- ca: [process.env.CA_CERT],
- key: process.env.CLIENT_KEY,
- cert: process.env.CLIENT_CERT,
- },
- sasl: {
- mechanism: 'plain',
- username,
- password,
- },
- }
- : {};
-
- const client: Kafka = new Kafka({
- clientId: 'umami',
- brokers: brokers,
- connectionTimeout: 3000,
- logLevel: logLevel.ERROR,
- ...ssl,
- });
-
- if (process.env.NODE_ENV !== 'production') {
- global[KAFKA] = client;
- }
-
- log('Kafka initialized');
-
- return client;
-}
-
-async function getProducer(): Promise {
- const producer = kafka.producer();
- await producer.connect();
-
- if (process.env.NODE_ENV !== 'production') {
- global[KAFKA_PRODUCER] = producer;
- }
-
- log('Kafka producer initialized');
-
- return producer;
-}
-
-function getDateFormat(date: Date, format?: string): string {
- return dateFormat(date, format ? format : 'UTC:yyyy-mm-dd HH:MM:ss');
-}
-
-async function sendMessage(
- message: { [key: string]: string | number },
- topic: string,
-): Promise {
- await connect();
-
- return producer.send({
- topic,
- messages: [
- {
- value: JSON.stringify(message),
- },
- ],
- acks: -1,
- });
-}
-
-async function sendMessages(messages: { [key: string]: string | number }[], topic: string) {
- await connect();
-
- await producer.send({
- topic,
- messages: messages.map(a => {
- return { value: JSON.stringify(a) };
- }),
- acks: 1,
- });
-}
-
-async function connect(): Promise {
- if (!kafka) {
- kafka = process.env.KAFKA_URL && process.env.KAFKA_BROKER && (global[KAFKA] || getClient());
-
- if (kafka) {
- producer = global[KAFKA_PRODUCER] || (await getProducer());
- }
- }
-
- return kafka;
-}
-
-export default {
- enabled,
- client: kafka,
- producer,
- log,
- connect,
- getDateFormat,
- sendMessage,
- sendMessages,
-};
diff --git a/src/lib/lang.ts b/src/lib/lang.ts
deleted file mode 100644
index 2a448f63..00000000
--- a/src/lib/lang.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import {
- arSA,
- be,
- bn,
- cs,
- sk,
- da,
- de,
- el,
- enUS,
- enGB,
- es,
- fi,
- fr,
- faIR,
- he,
- hi,
- hr,
- id,
- it,
- ja,
- km,
- ko,
- lt,
- mn,
- ms,
- nb,
- nl,
- pl,
- pt,
- ptBR,
- ro,
- ru,
- sl,
- sv,
- ta,
- th,
- tr,
- uk,
- zhCN,
- zhTW,
- ca,
- hu,
- vi,
-} from 'date-fns/locale';
-
-export const languages = {
- 'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' },
- 'be-BY': { label: 'Беларуская', dateLocale: be },
- 'bn-BD': { label: 'বাংলা', dateLocale: bn },
- 'ca-ES': { label: 'Català', dateLocale: ca },
- 'cs-CZ': { label: 'Čeština', dateLocale: cs },
- 'da-DK': { label: 'Dansk', dateLocale: da },
- 'de-CH': { label: 'Schwiizerdütsch', dateLocale: de },
- 'de-DE': { label: 'Deutsch', dateLocale: de },
- 'el-GR': { label: 'Ελληνικά', dateLocale: el },
- 'en-GB': { label: 'English (UK)', dateLocale: enGB },
- 'en-US': { label: 'English (US)', dateLocale: enUS },
- 'es-MX': { label: 'Español', dateLocale: es },
- 'fa-IR': { label: 'فارسی', dateLocale: faIR, dir: 'rtl' },
- 'fi-FI': { label: 'Suomi', dateLocale: fi },
- 'fo-FO': { label: 'Føroyskt' },
- 'fr-FR': { label: 'Français', dateLocale: fr },
- 'ga-ES': { label: 'Galacian (Spain)', dateLocale: es },
- 'he-IL': { label: 'עברית', dateLocale: he },
- 'hi-IN': { label: 'हिन्दी', dateLocale: hi },
- 'hr-HR': { label: 'Hrvatski', dateLocale: hr },
- 'hu-HU': { label: 'Hungarian', dateLocale: hu },
- 'id-ID': { label: 'Bahasa Indonesia', dateLocale: id },
- 'it-IT': { label: 'Italiano', dateLocale: it },
- 'ja-JP': { label: '日本語', dateLocale: ja },
- 'km-KH': { label: 'ភាសាខ្មែរ', dateLocale: km },
- 'ko-KR': { label: '한국어', dateLocale: ko },
- 'lt-LT': { label: 'Lietuvių', dateLocale: lt },
- 'mn-MN': { label: 'Монгол', dateLocale: mn },
- 'ms-MY': { label: 'Malay', dateLocale: ms },
- 'my-MM': { label: 'မြန်မာဘာသာ', dateLocale: enUS },
- 'nl-NL': { label: 'Nederlands', dateLocale: nl },
- 'nb-NO': { label: 'Norsk Bokmål', dateLocale: nb },
- 'pl-PL': { label: 'Polski', dateLocale: pl },
- 'pt-BR': { label: 'Português do Brasil', dateLocale: ptBR },
- 'pt-PT': { label: 'Português', dateLocale: pt },
- 'ro-RO': { label: 'Română', dateLocale: ro },
- 'ru-RU': { label: 'Русский', dateLocale: ru },
- 'si-LK': { label: 'සිංහල', dateLocale: id },
- 'sk-SK': { label: 'Slovenčina', dateLocale: sk },
- 'sl-SI': { label: 'Slovenščina', dateLocale: sl },
- 'sv-SE': { label: 'Svenska', dateLocale: sv },
- 'ta-IN': { label: 'தமிழ்', dateLocale: ta },
- 'th-TH': { label: 'ภาษาไทย', dateLocale: th },
- 'tr-TR': { label: 'Türkçe', dateLocale: tr },
- 'uk-UA': { label: 'українська', dateLocale: uk },
- 'ur-PK': { label: 'Urdu (Pakistan)', dateLocale: uk, dir: 'rtl' },
- 'vi-VN': { label: 'Tiếng Việt', dateLocale: vi },
- 'zh-CN': { label: '中文', dateLocale: zhCN },
- 'zh-TW': { label: '中文(繁體)', dateLocale: zhTW },
-};
-
-export function getDateLocale(locale: string) {
- return languages[locale]?.dateLocale || enUS;
-}
-
-export function getTextDirection(locale: string) {
- return languages[locale]?.dir || 'ltr';
-}
diff --git a/src/lib/load.ts b/src/lib/load.ts
deleted file mode 100644
index d980f8e9..00000000
--- a/src/lib/load.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import cache from 'lib/cache';
-import { getSession, getUserById, getWebsiteById } from 'queries';
-import { User, Website, Session } from '@prisma/client';
-
-export async function loadWebsite(websiteId: string): Promise {
- let website;
-
- if (cache.enabled) {
- website = await cache.fetchWebsite(websiteId);
- } else {
- website = await getWebsiteById(websiteId);
- }
-
- if (!website || website.deletedAt) {
- return null;
- }
-
- return website;
-}
-
-export async function loadSession(sessionId: string): Promise {
- let session;
-
- if (cache.enabled) {
- session = await cache.fetchSession(sessionId);
- } else {
- session = await getSession(sessionId);
- }
-
- if (!session) {
- return null;
- }
-
- return session;
-}
-
-export async function loadUser(userId: string): Promise {
- let user;
-
- if (cache.enabled) {
- user = await cache.fetchUser(userId);
- } else {
- user = await getUserById(userId);
- }
-
- if (!user || user.deletedAt) {
- return null;
- }
-
- return user;
-}
diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts
deleted file mode 100644
index 91fb6c7c..00000000
--- a/src/lib/middleware.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import cors from 'cors';
-import debug from 'debug';
-import redis from '@umami/redis-client';
-import { getAuthToken, parseShareToken } from 'lib/auth';
-import { ROLES } from 'lib/constants';
-import { secret } from 'lib/crypto';
-import { findSession } from 'lib/session';
-import {
- badRequest,
- createMiddleware,
- forbidden,
- parseSecureToken,
- tooManyRequest,
- unauthorized,
-} from 'next-basics';
-import { NextApiRequestCollect } from 'pages/api/send';
-import { getUserById } from '../queries';
-
-const log = debug('umami:middleware');
-
-export const useCors = createMiddleware(
- cors({
- // Cache CORS preflight request 24 hours by default
- maxAge: Number(process.env.CORS_MAX_AGE) || 86400,
- }),
-);
-
-export const useSession = createMiddleware(async (req, res, next) => {
- try {
- const session = await findSession(req as NextApiRequestCollect);
-
- if (!session) {
- log('useSession: Session not found');
- return badRequest(res, 'Session not found.');
- }
-
- (req as any).session = session;
- } catch (e: any) {
- if (e.message === 'Usage Limit.') {
- return tooManyRequest(res, e.message);
- }
- if (e.message.startsWith('Website not found:')) {
- return forbidden(res, e.message);
- }
- return badRequest(res, e.message);
- }
-
- next();
-});
-
-export const useAuth = createMiddleware(async (req, res, next) => {
- const token = getAuthToken(req);
- const payload = parseSecureToken(token, secret());
- const shareToken = await parseShareToken(req as any);
-
- let user = null;
- const { userId, authKey, grant } = payload || {};
-
- if (userId) {
- user = await getUserById(userId);
- } else if (redis.enabled && authKey) {
- const key = await redis.client.get(authKey);
-
- if (key?.userId) {
- user = await getUserById(key.userId);
- }
- }
-
- if (process.env.NODE_ENV === 'development') {
- log('useAuth:', { token, shareToken, payload, user, grant });
- }
-
- if (!user?.id && !shareToken) {
- log('useAuth: User not authorized');
- return unauthorized(res);
- }
-
- if (user) {
- user.isAdmin = user.role === ROLES.admin;
- }
-
- (req as any).auth = {
- user,
- grant,
- token,
- shareToken,
- authKey,
- };
-
- next();
-});
-
-export const useValidate = async (schema, req, res) => {
- return createMiddleware(async (req: any, res, next) => {
- try {
- const rules = schema[req.method];
-
- if (rules) {
- rules.validateSync({ ...req.query, ...req.body });
- }
- } catch (e: any) {
- return badRequest(res, e.message);
- }
-
- next();
- })(req, res);
-};
diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts
deleted file mode 100644
index cb119bb8..00000000
--- a/src/lib/prisma.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import { Prisma } from '@prisma/client';
-import prisma from '@umami/prisma-client';
-import moment from 'moment-timezone';
-import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
-import { FILTER_COLUMNS, SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants';
-import { loadWebsite } from './load';
-import { maxDate } from './date';
-import { QueryFilters, QueryOptions, SearchFilter } from './types';
-
-const MYSQL_DATE_FORMATS = {
- minute: '%Y-%m-%d %H:%i:00',
- hour: '%Y-%m-%d %H:00:00',
- day: '%Y-%m-%d',
- month: '%Y-%m-01',
- year: '%Y-01-01',
-};
-
-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',
-};
-
-function getAddIntervalQuery(field: string, interval: string): string {
- const db = getDatabaseType(process.env.DATABASE_URL);
-
- if (db === POSTGRESQL) {
- return `${field} + interval '${interval}'`;
- }
-
- if (db === MYSQL) {
- return `DATE_ADD(${field}, interval ${interval})`;
- }
-}
-
-function getDayDiffQuery(field1: string, field2: string): string {
- const db = getDatabaseType(process.env.DATABASE_URL);
-
- if (db === POSTGRESQL) {
- return `${field1}::date - ${field2}::date`;
- }
-
- if (db === MYSQL) {
- return `DATEDIFF(${field1}, ${field2})`;
- }
-}
-
-function getCastColumnQuery(field: string, type: string): string {
- const db = getDatabaseType(process.env.DATABASE_URL);
-
- if (db === POSTGRESQL) {
- return `${field}::${type}`;
- }
-
- if (db === MYSQL) {
- return `${field}`;
- }
-}
-
-function getDateQuery(field: string, unit: string, timezone?: string): string {
- const db = getDatabaseType();
-
- 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]}')`;
- }
-}
-
-function getTimestampDiffQuery(field1: string, field2: string): string {
- const db = getDatabaseType();
-
- if (db === POSTGRESQL) {
- return `floor(extract(epoch from (${field2} - ${field1})))`;
- }
-
- if (db === MYSQL) {
- return `timestampdiff(second, ${field1}, ${field2})`;
- }
-}
-
-function mapFilter(column, operator, name, type = 'varchar') {
- switch (operator) {
- case OPERATORS.equals:
- return `${column} = {{${name}::${type}}}`;
- case OPERATORS.notEquals:
- return `${column} != {{${name}::${type}}}`;
- default:
- return '';
- }
-}
-
-function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}): string {
- const query = Object.keys(filters).reduce((arr, name) => {
- const value = filters[name];
- const operator = value?.filter ?? OPERATORS.equals;
- const column = FILTER_COLUMNS[name] ?? options?.columns?.[name];
-
- if (value !== undefined && column) {
- arr.push(`and ${mapFilter(column, operator, name)}`);
-
- if (name === 'referrer') {
- arr.push(
- 'and (website_event.referrer_domain != {{websiteDomain}} or website_event.referrer_domain is null)',
- );
- }
- }
-
- return arr;
- }, []);
-
- return query.join('\n');
-}
-
-function normalizeFilters(filters = {}) {
- return Object.keys(filters).reduce((obj, key) => {
- const value = filters[key];
-
- obj[key] = value?.value ?? value;
-
- return obj;
- }, {});
-}
-
-async function parseFilters(
- websiteId: string,
- filters: QueryFilters = {},
- options: QueryOptions = {},
-) {
- const website = await loadWebsite(websiteId);
-
- return {
- joinSession:
- options?.joinSession || Object.keys(filters).find(key => SESSION_COLUMNS.includes(key))
- ? `inner join session on website_event.session_id = session.session_id`
- : '',
- filterQuery: getFilterQuery(filters, options),
- params: {
- ...normalizeFilters(filters),
- websiteId,
- startDate: maxDate(filters.startDate, website.resetAt),
- websiteDomain: website.domain,
- },
- };
-}
-
-async function rawQuery(sql: string, data: object): Promise {
- const db = getDatabaseType();
- const params = [];
-
- if (db !== POSTGRESQL && db !== MYSQL) {
- return Promise.reject(new Error('Unknown database.'));
- }
-
- const query = sql?.replaceAll(/\{\{\s*(\w+)(::\w+)?\s*}}/g, (...args) => {
- const [, name, type] = args;
- params.push(data[name]);
-
- return db === MYSQL ? '?' : `$${params.length}${type ?? ''}`;
- });
-
- return prisma.rawQuery(query, params);
-}
-
-function getPageFilters(filters: SearchFilter): [
- {
- orderBy: {
- [x: string]: string;
- }[];
- take: number;
- skip: number;
- },
- {
- pageSize: number;
- page: number;
- orderBy: string;
- },
-] {
- const { page = 1, pageSize = DEFAULT_PAGE_SIZE, orderBy, sortDescending = false } = filters || {};
-
- return [
- {
- ...(pageSize > 0 && { take: +pageSize, skip: +pageSize * (page - 1) }),
- ...(orderBy && {
- orderBy: [
- {
- [orderBy]: sortDescending ? 'desc' : 'asc',
- },
- ],
- }),
- },
- { page: +page, pageSize, orderBy },
- ];
-}
-
-function getSearchMode(): { mode?: Prisma.QueryMode } {
- const db = getDatabaseType();
-
- if (db === POSTGRESQL) {
- return {
- mode: 'insensitive',
- };
- }
-
- return {};
-}
-
-export default {
- ...prisma,
- getAddIntervalQuery,
- getDayDiffQuery,
- getCastColumnQuery,
- getDateQuery,
- getTimestampDiffQuery,
- getFilterQuery,
- parseFilters,
- getPageFilters,
- getSearchMode,
- rawQuery,
-};
diff --git a/src/lib/query.ts b/src/lib/query.ts
deleted file mode 100644
index 88ce62d4..00000000
--- a/src/lib/query.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { NextApiRequest } from 'next';
-import { getAllowedUnits, getMinimumUnit } from './date';
-import { getWebsiteDateRange } from '../queries';
-
-export async function parseDateRangeQuery(req: NextApiRequest) {
- const { id: websiteId, startAt, endAt, unit } = req.query;
-
- // All-time
- if (+startAt === 0 && +endAt === 1) {
- const result = await getWebsiteDateRange(websiteId as string);
- const { min, max } = result[0];
- const startDate = new Date(min);
- const endDate = new Date(max);
-
- return {
- startDate,
- endDate,
- unit: getMinimumUnit(startDate, endDate),
- };
- }
-
- const startDate = new Date(+startAt);
- const endDate = new Date(+endAt);
- const minUnit = getMinimumUnit(startDate, endDate);
-
- return {
- startDate,
- endDate,
- unit: (getAllowedUnits(startDate, endDate).includes(unit as string) ? unit : minUnit) as string,
- };
-}
diff --git a/src/lib/schema.ts b/src/lib/schema.ts
deleted file mode 100644
index c09d262a..00000000
--- a/src/lib/schema.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as yup from 'yup';
-
-export const dateRange = {
- startAt: yup.number().integer().required(),
- endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
-};
-
-export const pageInfo = {
- query: yup.string(),
- page: yup.number().integer().positive(),
- pageSize: yup.number().integer().positive().min(1).max(200),
- orderBy: yup.string(),
-};
diff --git a/src/lib/session.ts b/src/lib/session.ts
deleted file mode 100644
index 0f388db9..00000000
--- a/src/lib/session.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { isUuid, secret, uuid } from 'lib/crypto';
-import { getClientInfo } from 'lib/detect';
-import { parseToken } from 'next-basics';
-import { NextApiRequestCollect } from 'pages/api/send';
-import { createSession } from 'queries';
-import cache from './cache';
-import clickhouse from './clickhouse';
-import { loadSession, loadWebsite } from './load';
-
-export async function findSession(req: NextApiRequestCollect): Promise<{
- id: any;
- websiteId: string;
- hostname: string;
- browser: string;
- os: any;
- device: string;
- screen: string;
- language: string;
- country: any;
- subdivision1: any;
- subdivision2: any;
- city: any;
- ownerId: string;
-}> {
- const { payload } = req.body;
-
- if (!payload) {
- throw new Error('Invalid payload.');
- }
-
- // Check if cache token is passed
- const cacheToken = req.headers['x-umami-cache'];
-
- if (cacheToken) {
- const result = await parseToken(cacheToken, secret());
-
- if (result) {
- await checkUserBlock(result?.ownerId);
-
- return result;
- }
- }
-
- // Verify payload
- const { website: websiteId, hostname, screen, language } = payload;
-
- // Check the hostname value for legality to eliminate dirty data
- const validHostnameRegex = /^[\w-.]+$/;
- if (!validHostnameRegex.test(hostname)) {
- throw new Error('Invalid hostname.');
- }
-
- if (!isUuid(websiteId)) {
- throw new Error('Invalid website ID.');
- }
-
- // Find website
- const website = await loadWebsite(websiteId);
-
- if (!website) {
- throw new Error(`Website not found: ${websiteId}.`);
- }
-
- await checkUserBlock(website.userId);
-
- const { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device } =
- await getClientInfo(req, payload);
-
- const sessionId = uuid(websiteId, hostname, ip, userAgent);
-
- // Clickhouse does not require session lookup
- if (clickhouse.enabled) {
- return {
- id: sessionId,
- websiteId,
- hostname,
- browser,
- os: os as any,
- device,
- screen,
- language,
- country,
- subdivision1,
- subdivision2,
- city,
- ownerId: website.userId,
- };
- }
-
- // Find session
- let session = await loadSession(sessionId);
-
- // Create a session if not found
- if (!session) {
- try {
- session = await createSession({
- id: sessionId,
- websiteId,
- hostname,
- browser,
- os,
- device,
- screen,
- language,
- country,
- subdivision1,
- subdivision2,
- city,
- });
- } catch (e: any) {
- if (!e.message.toLowerCase().includes('unique constraint')) {
- throw e;
- }
- }
- }
-
- return { ...session, ownerId: website.userId };
-}
-
-async function checkUserBlock(userId: string) {
- if (process.env.ENABLE_BLOCKER && (await cache.fetchUserBlock(userId))) {
- await cache.incrementUserBlock(userId);
-
- throw new Error('Usage Limit.');
- }
-}
diff --git a/src/lib/sql.ts b/src/lib/sql.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/lib/types.ts b/src/lib/types.ts
deleted file mode 100644
index af0ea0f7..00000000
--- a/src/lib/types.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-import { NextApiRequest } from 'next';
-import {
- COLLECTION_TYPE,
- DATA_TYPE,
- EVENT_TYPE,
- KAFKA_TOPIC,
- PERMISSIONS,
- REPORT_TYPES,
- ROLES,
-} from './constants';
-import * as yup from 'yup';
-import { TIME_UNIT } from './date';
-
-type ObjectValues = T[keyof T];
-
-export type TimeUnit = ObjectValues;
-export type Permission = ObjectValues;
-
-export type CollectionType = ObjectValues;
-export type Role = ObjectValues;
-export type EventType = ObjectValues;
-export type DynamicDataType = ObjectValues;
-export type KafkaTopic = ObjectValues;
-export type ReportType = ObjectValues;
-
-export interface WebsiteSearchFilter extends SearchFilter {
- userId?: string;
- teamId?: string;
- includeTeams?: boolean;
- onlyTeams?: boolean;
-}
-
-export interface UserSearchFilter extends SearchFilter {
- teamId?: string;
-}
-
-export interface TeamSearchFilter extends SearchFilter {
- userId?: string;
-}
-
-export interface ReportSearchFilter extends SearchFilter {
- userId?: string;
- websiteId?: string;
- includeTeams?: boolean;
-}
-
-export interface SearchFilter {
- query?: string;
- page?: number;
- pageSize?: number;
- orderBy?: string;
- sortDescending?: boolean;
-}
-
-export interface FilterResult {
- data: T[];
- count: number;
- page: number;
- pageSize: number;
- orderBy?: string;
- sortDescending?: boolean;
-}
-
-export interface DynamicData {
- [key: string]: number | string | DynamicData | number[] | string[] | DynamicData[];
-}
-
-export interface Auth {
- user?: {
- id: string;
- username: string;
- role: string;
- isAdmin: boolean;
- };
- grant?: Permission[];
- shareToken?: {
- websiteId: string;
- };
-}
-
-export interface YupRequest {
- GET?: yup.ObjectSchema;
- POST?: yup.ObjectSchema;
- PUT?: yup.ObjectSchema;
- DELETE?: yup.ObjectSchema;
-}
-
-export interface NextApiRequestQueryBody extends NextApiRequest {
- auth?: Auth;
- query: TQuery & { [key: string]: string | string[] };
- body: TBody;
- headers: any;
- yup: YupRequest;
-}
-
-export interface NextApiRequestAuth extends NextApiRequest {
- auth?: Auth;
- headers: any;
-}
-
-export interface User {
- id: string;
- username: string;
- password?: string;
- role: string;
- createdAt?: Date;
-}
-
-export interface Website {
- id: string;
- userId: string;
- resetAt: Date;
- name: string;
- domain: string;
- shareId: string;
- createdAt: Date;
-}
-
-export interface Share {
- id: string;
- token: string;
-}
-
-export interface WebsiteActive {
- x: number;
-}
-
-export interface WebsiteMetric {
- x: string;
- y: number;
-}
-
-export interface WebsiteEventMetric {
- x: string;
- t: string;
- y: number;
-}
-
-export interface WebsiteEventData {
- eventName?: string;
- fieldName: string;
- dataType: number;
- fieldValue?: string;
- total: number;
-}
-
-export interface WebsitePageviews {
- pageviews: {
- t: string;
- y: number;
- };
- sessions: {
- t: string;
- y: number;
- };
-}
-
-export interface WebsiteStats {
- pageviews: { value: number; change: number };
- uniques: { value: number; change: number };
- bounces: { value: number; change: number };
- totalTime: { value: number; change: number };
-}
-
-export interface RealtimeInit {
- websites: Website[];
- token: string;
- data: RealtimeUpdate;
-}
-
-export interface RealtimeUpdate {
- pageviews: any[];
- sessions: any[];
- events: any[];
- timestamp: number;
-}
-
-export interface DateRange {
- startDate: Date;
- endDate: Date;
- value: string;
- unit?: TimeUnit;
- selectedUnit?: { num: number; unit: TimeUnit };
-}
-
-export interface QueryFilters {
- startDate?: Date;
- endDate?: Date;
- timezone?: string;
- unit?: string;
- 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 };
- limit?: number;
-}
-
-export interface RealtimeData {
- pageviews: any[];
- sessions: any[];
- events: any[];
- timestamp: number;
- countries?: any[];
- visitors?: any[];
-}
diff --git a/src/lib/yup.ts b/src/lib/yup.ts
deleted file mode 100644
index 4008e44f..00000000
--- a/src/lib/yup.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import moment from 'moment-timezone';
-import * as yup from 'yup';
-import { UNIT_TYPES } from './constants';
-
-export const TimezoneTest = yup
- .string()
- .default('UTC')
- .test(
- 'timezone',
- () => `Invalid timezone`,
- value => moment.tz.zone(value) !== null,
- );
-
-export const UnitTypeTest = yup.string().test(
- 'unit',
- () => `Invalid unit`,
- value => UNIT_TYPES.includes(value),
-);