diff --git a/README.md b/README.md
index 3f457631..1adfbfb3 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ DATABASE_URL=connection-url
```
The connection url is in the following format:
+
```
postgresql://username:mypassword@localhost:5432/mydb
@@ -48,7 +49,7 @@ mysql://username:mypassword@localhost:3306/mydb
yarn build
```
-The build step will also create tables in your database if you ae installing for the first time. It will also create a login account with username **admin** and password **umami**.
+The build step will also create tables in your database if you ae installing for the first time. It will also create a login user with username **admin** and password **umami**.
### Start the application
@@ -69,11 +70,13 @@ docker compose up
```
Alternatively, to pull just the Umami Docker image with PostgreSQL support:
+
```bash
docker pull docker.umami.dev/umami-software/umami:postgresql-latest
```
Or with MySQL support:
+
```bash
docker pull docker.umami.dev/umami-software/umami:mysql-latest
```
diff --git a/components/forms/ChangePasswordForm.js b/components/forms/ChangePasswordForm.js
index dcad6f17..8942ecf6 100644
--- a/components/forms/ChangePasswordForm.js
+++ b/components/forms/ChangePasswordForm.js
@@ -43,7 +43,7 @@ export default function ChangePasswordForm({ values, onSave, onClose }) {
const { user } = useUser();
const handleSubmit = async values => {
- const { ok, error } = await post(`/accounts/${user.accountUuid}/password`, values);
+ const { ok, error } = await post(`/users/${user.id}/password`, values);
if (ok) {
onSave();
diff --git a/components/forms/LoginForm.js b/components/forms/LoginForm.js
index 8c8aa09e..6a6560f1 100644
--- a/components/forms/LoginForm.js
+++ b/components/forms/LoginForm.js
@@ -42,9 +42,11 @@ export default function LoginForm() {
});
if (ok) {
- setItem(AUTH_TOKEN, data.token);
+ const { user, token } = data;
- setUser(data.user);
+ setItem(AUTH_TOKEN, token);
+
+ setUser(user);
await router.push('/');
diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js
index 52df3bde..5a098b8d 100644
--- a/components/forms/TrackingCodeForm.js
+++ b/components/forms/TrackingCodeForm.js
@@ -26,7 +26,7 @@ export default function TrackingCodeForm({ values, onClose }) {
rows={3}
cols={60}
spellCheck={false}
- defaultValue={``}
readOnly
diff --git a/components/forms/AccountEditForm.js b/components/forms/UserEditForm.js
similarity index 93%
rename from components/forms/AccountEditForm.js
rename to components/forms/UserEditForm.js
index 70125656..0d7e392f 100644
--- a/components/forms/AccountEditForm.js
+++ b/components/forms/UserEditForm.js
@@ -28,13 +28,13 @@ const validate = ({ id, username, password }) => {
return errors;
};
-export default function AccountEditForm({ values, onSave, onClose }) {
+export default function UserEditForm({ values, onSave, onClose }) {
const { post } = useApi();
const [message, setMessage] = useState();
const handleSubmit = async values => {
const { id } = values;
- const { ok, data } = await post(id ? `/accounts/${id}` : '/accounts', values);
+ const { ok, data } = await post(id ? `/users/${id}` : '/users', values);
if (ok) {
onSave();
diff --git a/components/forms/WebsiteEditForm.js b/components/forms/WebsiteEditForm.js
index f13d0aa0..491a8bfe 100644
--- a/components/forms/WebsiteEditForm.js
+++ b/components/forms/WebsiteEditForm.js
@@ -37,7 +37,7 @@ const validate = ({ name, domain }) => {
return errors;
};
-const OwnerDropDown = ({ user, accounts }) => {
+const OwnerDropDown = ({ user, users }) => {
const { setFieldValue, values } = useFormikContext();
useEffect(() => {
@@ -46,7 +46,7 @@ const OwnerDropDown = ({ user, accounts }) => {
} else if (user?.id && values.owner === '') {
setFieldValue('owner', user.id.toString());
}
- }, [accounts, setFieldValue, user, values]);
+ }, [users, setFieldValue, user, values]);
if (user?.isAdmin) {
return (
@@ -56,7 +56,7 @@ const OwnerDropDown = ({ user, accounts }) => {
- {accounts?.map(acc => (
+ {users?.map(acc => (
@@ -73,14 +73,14 @@ const OwnerDropDown = ({ user, accounts }) => {
export default function WebsiteEditForm({ values, onSave, onClose }) {
const { post } = useApi();
- const { data: accounts } = useFetch(`/accounts`);
+ const { data: users } = useFetch(`/users`);
const { user } = useUser();
const [message, setMessage] = useState();
const handleSubmit = async values => {
- const { websiteUuid: websiteId } = values;
+ const { id } = values;
- const { ok, data } = await post(websiteId ? `/websites/${websiteId}` : '/websites', values);
+ const { ok, data } = await post(id ? `/websites/${id}` : '/websites', values);
if (ok) {
onSave();
@@ -125,7 +125,7 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
-
+
diff --git a/components/layout/Header.js b/components/layout/Header.js
index a81e016c..13c3954e 100644
--- a/components/layout/Header.js
+++ b/components/layout/Header.js
@@ -13,11 +13,12 @@ import useConfig from 'hooks/useConfig';
import useUser from 'hooks/useUser';
import Logo from 'assets/logo.svg';
import styles from './Header.module.css';
+import SettingsButton from '../settings/SettingsButton';
export default function Header() {
const { user } = useUser();
const { pathname } = useRouter();
- const { updatesDisabled } = useConfig();
+ const { updatesDisabled, adminDisabled } = useConfig();
const isSharePage = pathname.includes('/share/');
const allowUpdate = user?.isAdmin && !updatesDisabled && !isSharePage;
@@ -30,7 +31,7 @@ export default function Header() {
umami
- {user && (
+ {user && !adminDisabled && (
@@ -38,17 +39,16 @@ export default function Header() {
- {!process.env.isCloudMode && (
-
-
-
- )}
+
+
+
)}
- {user && }
+
+ {user && !adminDisabled && }
>
diff --git a/components/layout/MenuLayout.js b/components/layout/MenuLayout.js
index 7183ad10..c5d86b92 100644
--- a/components/layout/MenuLayout.js
+++ b/components/layout/MenuLayout.js
@@ -17,9 +17,9 @@ export default function MenuLayout({
function handleSelect(url) {
if (replace) {
- router.replace(url);
+ router.replace(url, undefined, { shallow: true });
} else {
- router.push(url);
+ router.push(url, undefined, { shallow: true });
}
}
diff --git a/components/metrics/MetricCard.js b/components/metrics/MetricCard.js
index eb16904c..ecb52e1e 100644
--- a/components/metrics/MetricCard.js
+++ b/components/metrics/MetricCard.js
@@ -1,6 +1,6 @@
import React from 'react';
import { useSpring, animated } from 'react-spring';
-import { formatNumber } from '../../lib/format';
+import { formatNumber } from 'lib/format';
import styles from './MetricCard.module.css';
const MetricCard = ({
diff --git a/components/metrics/RealtimeHeader.js b/components/metrics/RealtimeHeader.js
index 1b93cd2f..d3c43dde 100644
--- a/components/metrics/RealtimeHeader.js
+++ b/components/metrics/RealtimeHeader.js
@@ -14,9 +14,9 @@ export default function RealtimeHeader({ websites, data, websiteId, onSelect })
value: null,
},
].concat(
- websites.map(({ name, websiteUuid }, index) => ({
+ websites.map(({ name, id }, index) => ({
label: name,
- value: websiteUuid,
+ value: id,
divider: index === 0,
})),
);
diff --git a/components/pages/DashboardEdit.js b/components/pages/DashboardEdit.js
index 0cc6d4e7..6af1644a 100644
--- a/components/pages/DashboardEdit.js
+++ b/components/pages/DashboardEdit.js
@@ -24,7 +24,7 @@ export default function DashboardEdit({ websites }) {
const ordered = useMemo(
() =>
websites
- .map(website => ({ ...website, order: order.indexOf(website.websiteUuid) }))
+ .map(website => ({ ...website, order: order.indexOf(website.id) }))
.sort(firstBy('order')),
[websites, order],
);
@@ -36,7 +36,7 @@ export default function DashboardEdit({ websites }) {
const [removed] = orderedWebsites.splice(source.index, 1);
orderedWebsites.splice(destination.index, 0, removed);
- setOrder(orderedWebsites.map(website => website?.websiteUuid || 0));
+ setOrder(orderedWebsites.map(website => website?.id || 0));
}
function handleSave() {
@@ -76,12 +76,8 @@ export default function DashboardEdit({ websites }) {
ref={provided.innerRef}
style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }}
>
- {ordered.map(({ websiteUuid, name, domain }, index) => (
-
+ {ordered.map(({ id, name, domain }, index) => (
+
{(provided, snapshot) => (
n.websiteUuid === websiteUuid);
+ if (websiteId) {
+ const { id } = init.websites.find(n => n.id === websiteId);
return {
pageviews: filterWebsite(pageviews, id),
sessions: filterWebsite(sessions, id),
@@ -61,7 +61,7 @@ export default function RealtimeDashboard() {
}
return data;
- }, [data, websiteUuid]);
+ }, [data, websiteId]);
const countries = useMemo(() => {
if (realtimeData?.sessions) {
@@ -118,9 +118,9 @@ export default function RealtimeDashboard() {
@@ -128,10 +128,10 @@ export default function RealtimeDashboard() {
-
+
-
+
diff --git a/components/pages/Settings.js b/components/pages/Settings.js
index 87b4ec9c..bc5022b9 100644
--- a/components/pages/Settings.js
+++ b/components/pages/Settings.js
@@ -4,12 +4,12 @@ import { useRouter } from 'next/router';
import Page from 'components/layout/Page';
import MenuLayout from 'components/layout/MenuLayout';
import WebsiteSettings from 'components/settings/WebsiteSettings';
-import AccountSettings from 'components/settings/AccountSettings';
+import UserSettings from 'components/settings/UserSettings';
import ProfileSettings from 'components/settings/ProfileSettings';
import useUser from 'hooks/useUser';
const WEBSITES = '/settings';
-const ACCOUNTS = '/settings/accounts';
+const ACCOUNTS = '/settings/users';
const PROFILE = '/settings/profile';
export default function Settings() {
@@ -28,7 +28,7 @@ export default function Settings() {
value: WEBSITES,
},
{
- label: ,
+ label: ,
value: ACCOUNTS,
hidden: !user?.isAdmin,
},
@@ -42,7 +42,7 @@ export default function Settings() {
{pathname === WEBSITES && }
- {pathname === ACCOUNTS && }
+ {pathname === ACCOUNTS && }
{pathname === PROFILE && }
diff --git a/components/pages/TestConsole.js b/components/pages/TestConsole.js
index 6200f7e5..4e80fcf8 100644
--- a/components/pages/TestConsole.js
+++ b/components/pages/TestConsole.js
@@ -12,7 +12,7 @@ import useFetch from 'hooks/useFetch';
import styles from './TestConsole.module.css';
export default function TestConsole() {
- const { data } = useFetch('/websites');
+ const { data } = useFetch('/websites?include_all=true');
const router = useRouter();
const {
basePath,
@@ -24,9 +24,9 @@ export default function TestConsole() {
return null;
}
- const options = data.map(({ name, websiteUuid }) => ({ label: name, value: websiteUuid }));
- const website = data.find(({ websiteUuid }) => websiteId === websiteUuid);
- const selectedValue = options.find(({ value }) => value === website?.websiteUuid)?.value;
+ const options = data.map(({ name, id }) => ({ label: name, value: id }));
+ const website = data.find(({ id }) => websiteId === id);
+ const selectedValue = options.find(({ value }) => value === website?.id)?.value;
function handleSelect(value) {
router.push(`/console/${value}`);
@@ -46,7 +46,7 @@ export default function TestConsole() {
@@ -104,13 +104,13 @@ export default function TestConsole() {
>
diff --git a/components/pages/WebsiteList.js b/components/pages/WebsiteList.js
index 24ac8bec..dfd041a1 100644
--- a/components/pages/WebsiteList.js
+++ b/components/pages/WebsiteList.js
@@ -27,7 +27,7 @@ export default function WebsiteList({ websites, showCharts, limit }) {
const ordered = useMemo(
() =>
websites
- .map(website => ({ ...website, order: websiteOrder.indexOf(website.websiteUuid) || 0 }))
+ .map(website => ({ ...website, order: websiteOrder.indexOf(website.id) || 0 }))
.sort(firstBy('order')),
[websites, websiteOrder],
);
@@ -46,11 +46,11 @@ export default function WebsiteList({ websites, showCharts, limit }) {
return (
- {ordered.map(({ websiteUuid, name, domain }, index) =>
+ {ordered.map(({ id, name, domain }, index) =>
index < limit ? (
-
+
!state);
+ }
+
+ useDocumentClick(e => {
+ if (!ref.current?.contains(e.target)) {
+ setShow(false);
+ }
+ });
+
+ return (
+
+
} variant="light" onClick={handleClick} />
+ {show && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/components/settings/SettingsButton.module.css b/components/settings/SettingsButton.module.css
new file mode 100644
index 00000000..9f1b8cdb
--- /dev/null
+++ b/components/settings/SettingsButton.module.css
@@ -0,0 +1,20 @@
+.button {
+ position: relative;
+}
+
+.panel {
+ background: var(--gray50);
+ border: 1px solid var(--gray500);
+ border-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ top: 100%;
+ right: 0;
+ padding: 20px;
+ z-index: 100;
+}
+
+.panel dd {
+ display: flex;
+}
diff --git a/components/settings/UserButton.js b/components/settings/UserButton.js
index 0d67bbd3..1d002c4b 100644
--- a/components/settings/UserButton.js
+++ b/components/settings/UserButton.js
@@ -8,10 +8,12 @@ import User from 'assets/user.svg';
import styles from './UserButton.module.css';
import { AUTH_TOKEN } from 'lib/constants';
import useUser from 'hooks/useUser';
+import useConfig from 'hooks/useConfig';
export default function UserButton() {
const { user } = useUser();
const router = useRouter();
+ const { adminDisabled } = useConfig();
const menuOptions = [
{
@@ -28,7 +30,7 @@ export default function UserButton() {
{
label: ,
value: 'profile',
- hidden: process.env.isCloudMode,
+ hidden: adminDisabled,
},
{ label: , value: 'logout' },
];
diff --git a/components/settings/AccountSettings.js b/components/settings/UserSettings.js
similarity index 68%
rename from components/settings/AccountSettings.js
rename to components/settings/UserSettings.js
index eebdae40..1fd5d0c5 100644
--- a/components/settings/AccountSettings.js
+++ b/components/settings/UserSettings.js
@@ -8,7 +8,7 @@ import Icon from 'components/common/Icon';
import Table from 'components/common/Table';
import Modal from 'components/common/Modal';
import Toast from 'components/common/Toast';
-import AccountEditForm from 'components/forms/AccountEditForm';
+import UserEditForm from 'components/forms/UserEditForm';
import ButtonLayout from 'components/layout/ButtonLayout';
import DeleteForm from 'components/forms/DeleteForm';
import useFetch from 'hooks/useFetch';
@@ -17,21 +17,21 @@ import Plus from 'assets/plus.svg';
import Trash from 'assets/trash.svg';
import Check from 'assets/check.svg';
import LinkIcon from 'assets/external-link.svg';
-import styles from './AccountSettings.module.css';
+import styles from './UserSettings.module.css';
-export default function AccountSettings() {
- const [addAccount, setAddAccount] = useState();
- const [editAccount, setEditAccount] = useState();
- const [deleteAccount, setDeleteAccount] = useState();
+export default function UserSettings() {
+ const [addUser, setAddUser] = useState();
+ const [editUser, setEditUser] = useState();
+ const [deleteUser, setDeleteUser] = useState();
const [saved, setSaved] = useState(0);
const [message, setMessage] = useState();
- const { data } = useFetch(`/accounts`, {}, [saved]);
+ const { data } = useFetch(`/users`, {}, [saved]);
const Checkmark = ({ isAdmin }) => (isAdmin ? } size="medium" /> : null);
const DashboardLink = row => {
return (
-
+
} />
@@ -41,11 +41,11 @@ export default function AccountSettings() {
const Buttons = row => (
- } size="small" onClick={() => setEditAccount(row)}>
+ } size="small" onClick={() => setEditUser(row)}>
{!row.isAdmin && (
- } size="small" onClick={() => setDeleteAccount(row)}>
+ } size="small" onClick={() => setDeleteUser(row)}>
)}
@@ -84,9 +84,9 @@ export default function AccountSettings() {
}
function handleClose() {
- setEditAccount(null);
- setAddAccount(null);
- setDeleteAccount(null);
+ setEditUser(null);
+ setAddUser(null);
+ setDeleteUser(null);
}
if (!data) {
@@ -97,33 +97,31 @@ export default function AccountSettings() {
<>
-
+
- } size="small" onClick={() => setAddAccount(true)}>
-
+ } size="small" onClick={() => setAddUser(true)}>
+
- {editAccount && (
- }>
- }>
+
)}
- {addAccount && (
- }>
-
+ {addUser && (
+ }>
+
)}
- {deleteAccount && (
- }
- >
+ {deleteUser && (
+ }>
diff --git a/components/settings/AccountSettings.module.css b/components/settings/UserSettings.module.css
similarity index 100%
rename from components/settings/AccountSettings.module.css
rename to components/settings/UserSettings.module.css
diff --git a/components/settings/WebsiteSettings.js b/components/settings/WebsiteSettings.js
index 80bd1f60..0b428527 100644
--- a/components/settings/WebsiteSettings.js
+++ b/components/settings/WebsiteSettings.js
@@ -46,7 +46,7 @@ export default function WebsiteSettings() {
icon={}
size="small"
tooltip={}
- tooltipId={`button-share-${row.websiteUuid}`}
+ tooltipId={`button-share-${row.id}`}
onClick={() => setShowUrl(row)}
/>
)}
@@ -56,46 +56,42 @@ export default function WebsiteSettings() {
tooltip={
}
- tooltipId={`button-code-${row.websiteUuid}`}
+ tooltipId={`button-code-${row.id}`}
onClick={() => setShowCode(row)}
/>
}
size="small"
tooltip={}
- tooltipId={`button-edit-${row.websiteUuid}`}
+ tooltipId={`button-edit-${row.id}`}
onClick={() => setEditWebsite(row)}
/>
}
size="small"
tooltip={}
- tooltipId={`button-reset-${row.websiteUuid}`}
+ tooltipId={`button-reset-${row.id}`}
onClick={() => setResetWebsite(row)}
/>
}
size="small"
tooltip={}
- tooltipId={`button-delete-${row.websiteUuid}`}
+ tooltipId={`button-delete-${row.id}`}
onClick={() => setDeleteWebsite(row)}
/>
);
- const DetailsLink = ({ websiteUuid, name, domain }) => (
-
+ const DetailsLink = ({ id, name, domain }) => (
+
- {name}
+ {name}
);
- const Domain = ({ domain, websiteUuid }) => (
- {domain}
+ const Domain = ({ domain, id }) => (
+ {domain}
);
const adminColumns = [
@@ -112,7 +108,7 @@ export default function WebsiteSettings() {
render: Domain,
},
{
- key: 'account',
+ key: 'user',
label: ,
className: 'col-12 col-lg-4 col-xl-1',
},
@@ -203,7 +199,7 @@ export default function WebsiteSettings() {
title={}
>
@@ -214,7 +210,7 @@ export default function WebsiteSettings() {
title={}
>
diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql
index b33ce2f6..0579d13d 100644
--- a/db/clickhouse/schema.sql
+++ b/db/clickhouse/schema.sql
@@ -6,6 +6,7 @@ CREATE TABLE event
website_id UUID,
session_id UUID,
event_id Nullable(UUID),
+ rev_id UInt32,
--session
hostname LowCardinality(String),
browser LowCardinality(String),
@@ -30,6 +31,7 @@ CREATE TABLE event_queue (
website_id UUID,
session_id UUID,
event_id Nullable(UUID),
+ rev_id UInt32,
url String,
referrer String,
hostname LowCardinality(String),
@@ -55,6 +57,7 @@ CREATE MATERIALIZED VIEW event_queue_mv TO event AS
SELECT website_id,
session_id,
event_id,
+ rev_id,
url,
referrer,
hostname,
diff --git a/db/postgresql/migrations/01_init/migration.sql b/db/postgresql/migrations/01_init/migration.sql
deleted file mode 100644
index cdabcd17..00000000
--- a/db/postgresql/migrations/01_init/migration.sql
+++ /dev/null
@@ -1,132 +0,0 @@
--- CreateTable
-CREATE TABLE "account" (
- "user_id" SERIAL NOT NULL,
- "username" VARCHAR(255) NOT NULL,
- "password" VARCHAR(60) NOT NULL,
- "is_admin" BOOLEAN NOT NULL DEFAULT false,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "updated_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
-
- PRIMARY KEY ("user_id")
-);
-
--- CreateTable
-CREATE TABLE "event" (
- "event_id" SERIAL NOT NULL,
- "website_id" INTEGER NOT NULL,
- "session_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "url" VARCHAR(500) NOT NULL,
- "event_type" VARCHAR(50) NOT NULL,
- "event_value" VARCHAR(50) NOT NULL,
-
- PRIMARY KEY ("event_id")
-);
-
--- CreateTable
-CREATE TABLE "pageview" (
- "view_id" SERIAL NOT NULL,
- "website_id" INTEGER NOT NULL,
- "session_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "url" VARCHAR(500) NOT NULL,
- "referrer" VARCHAR(500),
-
- PRIMARY KEY ("view_id")
-);
-
--- CreateTable
-CREATE TABLE "session" (
- "session_id" SERIAL NOT NULL,
- "session_uuid" UUID NOT NULL,
- "website_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "hostname" VARCHAR(100),
- "browser" VARCHAR(20),
- "os" VARCHAR(20),
- "device" VARCHAR(20),
- "screen" VARCHAR(11),
- "language" VARCHAR(35),
- "country" CHAR(2),
-
- PRIMARY KEY ("session_id")
-);
-
--- CreateTable
-CREATE TABLE "website" (
- "website_id" SERIAL NOT NULL,
- "website_uuid" UUID NOT NULL,
- "user_id" INTEGER NOT NULL,
- "name" VARCHAR(100) NOT NULL,
- "domain" VARCHAR(500),
- "share_id" VARCHAR(64),
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
-
- PRIMARY KEY ("website_id")
-);
-
--- CreateIndex
-CREATE UNIQUE INDEX "account.username_unique" ON "account"("username");
-
--- CreateIndex
-CREATE INDEX "event_created_at_idx" ON "event"("created_at");
-
--- CreateIndex
-CREATE INDEX "event_session_id_idx" ON "event"("session_id");
-
--- CreateIndex
-CREATE INDEX "event_website_id_idx" ON "event"("website_id");
-
--- CreateIndex
-CREATE INDEX "pageview_created_at_idx" ON "pageview"("created_at");
-
--- CreateIndex
-CREATE INDEX "pageview_session_id_idx" ON "pageview"("session_id");
-
--- CreateIndex
-CREATE INDEX "pageview_website_id_created_at_idx" ON "pageview"("website_id", "created_at");
-
--- CreateIndex
-CREATE INDEX "pageview_website_id_idx" ON "pageview"("website_id");
-
--- CreateIndex
-CREATE INDEX "pageview_website_id_session_id_created_at_idx" ON "pageview"("website_id", "session_id", "created_at");
-
--- CreateIndex
-CREATE UNIQUE INDEX "session.session_uuid_unique" ON "session"("session_uuid");
-
--- CreateIndex
-CREATE INDEX "session_created_at_idx" ON "session"("created_at");
-
--- CreateIndex
-CREATE INDEX "session_website_id_idx" ON "session"("website_id");
-
--- CreateIndex
-CREATE UNIQUE INDEX "website.website_uuid_unique" ON "website"("website_uuid");
-
--- CreateIndex
-CREATE UNIQUE INDEX "website.share_id_unique" ON "website"("share_id");
-
--- CreateIndex
-CREATE INDEX "website_user_id_idx" ON "website"("user_id");
-
--- AddForeignKey
-ALTER TABLE "event" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "event" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "pageview" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "pageview" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "session" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "website" ADD FOREIGN KEY ("user_id") REFERENCES "account"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- CreateAdminUser
-INSERT INTO account (username, password, is_admin) values ('admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa', true);
diff --git a/db/postgresql/migrations/02_add_event_data/migration.sql b/db/postgresql/migrations/02_add_event_data/migration.sql
deleted file mode 100644
index 24c22e35..00000000
--- a/db/postgresql/migrations/02_add_event_data/migration.sql
+++ /dev/null
@@ -1,66 +0,0 @@
--- DropForeignKey
-ALTER TABLE "event" DROP CONSTRAINT "event_session_id_fkey";
-ALTER TABLE "event" DROP CONSTRAINT "event_website_id_fkey";
-
--- RenameIndex
-ALTER INDEX "event_pkey" RENAME TO "event_old_pkey";
-ALTER INDEX "event_created_at_idx" RENAME TO "event_old_created_at_idx";
-ALTER INDEX "event_session_id_idx" RENAME TO "event_old_session_id_idx";
-ALTER INDEX "event_website_id_idx" RENAME TO "event_old_website_id_idx";
-
--- RenameTable
-ALTER TABLE "event" RENAME TO "_event_old";
-
--- CreateTable
-CREATE TABLE "event" (
- "event_id" SERIAL NOT NULL,
- "website_id" INTEGER NOT NULL,
- "session_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "url" VARCHAR(500) NOT NULL,
- "event_name" VARCHAR(50) NOT NULL,
-
- PRIMARY KEY ("event_id")
-);
-
--- CreateIndex
-CREATE INDEX "event_created_at_idx" ON "event"("created_at");
-
--- CreateIndex
-CREATE INDEX "event_session_id_idx" ON "event"("session_id");
-
--- CreateIndex
-CREATE INDEX "event_website_id_idx" ON "event"("website_id");
-
--- AddForeignKey
-ALTER TABLE "event" ADD CONSTRAINT "event_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "event" ADD CONSTRAINT "event_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- CreateTable
-CREATE TABLE "event_data" (
- "event_data_id" SERIAL NOT NULL,
- "event_id" INTEGER NOT NULL,
- "event_data" JSONB NOT NULL,
-
- CONSTRAINT "event_data_pkey" PRIMARY KEY ("event_data_id")
-);
-
--- CreateIndex
-CREATE UNIQUE INDEX "event_data_event_id_key" ON "event_data"("event_id");
-
--- AddForeignKey
-ALTER TABLE "event_data" ADD CONSTRAINT "event_data_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "event"("event_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- RenameIndex
-ALTER INDEX IF EXISTS "account.username_unique" RENAME TO "account_username_key";
-
--- RenameIndex
-ALTER INDEX IF EXISTS "session.session_uuid_unique" RENAME TO "session_session_uuid_key";
-
--- RenameIndex
-ALTER INDEX IF EXISTS "website.share_id_unique" RENAME TO "website_share_id_key";
-
--- RenameIndex
-ALTER INDEX IF EXISTS "website.website_uuid_unique" RENAME TO "website_website_uuid_key";
\ No newline at end of file
diff --git a/db/postgresql/migrations/03_remove_casade_delete/migration.sql b/db/postgresql/migrations/03_remove_casade_delete/migration.sql
deleted file mode 100644
index aa1f7805..00000000
--- a/db/postgresql/migrations/03_remove_casade_delete/migration.sql
+++ /dev/null
@@ -1,35 +0,0 @@
--- DropForeignKey
-ALTER TABLE "event" DROP CONSTRAINT IF EXISTS "event_session_id_fkey";
-
--- DropForeignKey
-ALTER TABLE "event" DROP CONSTRAINT IF EXISTS "event_website_id_fkey";
-
--- DropForeignKey
-ALTER TABLE "pageview" DROP CONSTRAINT IF EXISTS "pageview_session_id_fkey";
-
--- DropForeignKey
-ALTER TABLE "pageview" DROP CONSTRAINT IF EXISTS "pageview_website_id_fkey";
-
--- DropForeignKey
-ALTER TABLE "session" DROP CONSTRAINT IF EXISTS "session_website_id_fkey";
-
--- DropForeignKey
-ALTER TABLE "website" DROP CONSTRAINT IF EXISTS "website_user_id_fkey";
-
--- AddForeignKey
-ALTER TABLE "event" ADD CONSTRAINT "event_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "event" ADD CONSTRAINT "event_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "pageview" ADD CONSTRAINT "pageview_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "pageview" ADD CONSTRAINT "pageview_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "session" ADD CONSTRAINT "session_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "website" ADD CONSTRAINT "website_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "account"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/db/postgresql/migrations/04_add_uuid/migration.sql b/db/postgresql/migrations/04_add_uuid/migration.sql
deleted file mode 100644
index 749cef6b..00000000
--- a/db/postgresql/migrations/04_add_uuid/migration.sql
+++ /dev/null
@@ -1,38 +0,0 @@
--- CreateExtension
-CREATE EXTENSION IF NOT EXISTS pgcrypto;
-
--- AlterTable
-ALTER TABLE "account" ADD COLUMN "account_uuid" UUID NULL;
-
--- Backfill UUID
-UPDATE "account" SET account_uuid = gen_random_uuid();
-
--- AlterTable
-ALTER TABLE "account" ALTER COLUMN "account_uuid" SET NOT NULL;
-
--- CreateIndex
-CREATE UNIQUE INDEX "account_account_uuid_key" ON "account"("account_uuid");
-
--- AlterTable
-ALTER TABLE "event" ADD COLUMN "event_uuid" UUID NULL;
-
--- Backfill UUID
-UPDATE "event" SET event_uuid = gen_random_uuid();
-
--- AlterTable
-ALTER TABLE "event" ALTER COLUMN "event_uuid" SET NOT NULL;
-
--- CreateIndex
-CREATE UNIQUE INDEX "event_event_uuid_key" ON "event"("event_uuid");
-
--- CreateIndex
-CREATE INDEX "account_account_uuid_idx" ON "account"("account_uuid");
-
--- CreateIndex
-CREATE INDEX "session_session_uuid_idx" ON "session"("session_uuid");
-
--- CreateIndex
-CREATE INDEX "website_website_uuid_idx" ON "website"("website_uuid");
-
--- CreateIndex
-CREATE INDEX "event_event_uuid_idx" ON "event"("event_uuid");
\ No newline at end of file
diff --git a/db/postgresql/migrations/migration_lock.toml b/db/postgresql/migrations/migration_lock.toml
deleted file mode 100644
index fbffa92c..00000000
--- a/db/postgresql/migrations/migration_lock.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-# Please do not edit this file manually
-# It should be added in your version-control system (i.e. Git)
-provider = "postgresql"
\ No newline at end of file
diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma
index ad1c7595..58b28a8b 100644
--- a/db/postgresql/schema.prisma
+++ b/db/postgresql/schema.prisma
@@ -7,97 +7,204 @@ datasource db {
url = env("DATABASE_URL")
}
-model account {
- id Int @id @default(autoincrement()) @map("user_id")
- username String @unique @db.VarChar(255)
- password String @db.VarChar(60)
- isAdmin Boolean @default(false) @map("is_admin")
- createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
- updatedAt DateTime? @default(now()) @map("updated_at") @db.Timestamptz(6)
- accountUuid String @unique @map("account_uuid") @db.Uuid
- website website[]
+model User {
+ id String @id @unique @map("user_id") @db.Uuid
+ username String @unique @db.VarChar(255)
+ password String @db.VarChar(60)
+ isAdmin Boolean @default(false) @map("is_admin")
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
- @@index([accountUuid])
+ groupRole GroupRole[]
+ groupUser GroupUser[]
+ userRole UserRole[]
+ teamWebsite TeamWebsite[]
+ teamUser TeamUser[]
+ userWebsite UserWebsite[]
+ website Website[]
+
+ @@map("user")
}
-model event {
- id Int @id() @default(autoincrement()) @map("event_id")
- websiteId Int @map("website_id")
- sessionId Int @map("session_id")
- createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
- url String @db.VarChar(500)
- eventName String @map("event_name") @db.VarChar(50)
- eventUuid String @unique @map("event_uuid") @db.Uuid
- session session @relation(fields: [sessionId], references: [id])
- website website @relation(fields: [websiteId], references: [id])
- eventData eventData?
+model Session {
+ id String @id @unique @map("session_id") @db.Uuid
+ websiteId String @map("website_id") @db.Uuid
+ hostname String? @db.VarChar(100)
+ browser String? @db.VarChar(20)
+ os String? @db.VarChar(20)
+ device String? @db.VarChar(20)
+ screen String? @db.VarChar(11)
+ language String? @db.VarChar(35)
+ country String? @db.Char(2)
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
@@index([createdAt])
- @@index([sessionId])
@@index([websiteId])
- @@index([eventUuid])
+ @@map("session")
}
-model eventData {
- id Int @id @default(autoincrement()) @map("event_data_id")
- eventId Int @unique @map("event_id")
- eventData Json @map("event_data")
- event event @relation(fields: [eventId], references: [id])
+model Website {
+ id String @id @unique @map("website_id") @db.Uuid
+ userId String @map("user_id") @db.Uuid
+ name String @db.VarChar(100)
+ domain String? @db.VarChar(500)
+ shareId String? @unique @map("share_id") @db.VarChar(64)
+ revId Int @default(0) @map("rev_id") @db.Integer
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
- @@map("event_data")
+ user User @relation(fields: [userId], references: [id])
+ teamWebsite TeamWebsite[]
+ userWebsite UserWebsite[]
+
+ @@index([userId])
+ @@index([createdAt])
+ @@index([shareId])
+ @@map("website")
}
-model pageview {
- id Int @id @default(autoincrement()) @map("view_id")
- websiteId Int @map("website_id")
- sessionId Int @map("session_id")
+model WebsiteEvent {
+ id String @id() @map("event_id") @db.Uuid
+ websiteId String @map("website_id") @db.Uuid
+ sessionId String @map("session_id") @db.Uuid
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
url String @db.VarChar(500)
referrer String? @db.VarChar(500)
- session session @relation(fields: [sessionId], references: [id])
- website website @relation(fields: [websiteId], references: [id])
+ eventName String @map("event_name") @db.VarChar(50)
+ eventData Json @map("event_data")
@@index([createdAt])
@@index([sessionId])
+ @@index([websiteId])
@@index([websiteId, createdAt])
- @@index([websiteId])
@@index([websiteId, sessionId, createdAt])
+ @@map("website_event")
}
-model session {
- id Int @id @default(autoincrement()) @map("session_id")
- sessionUuid String @unique @map("session_uuid") @db.Uuid
- websiteId Int @map("website_id")
- createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
- hostname String? @db.VarChar(100)
- browser String? @db.VarChar(20)
- os String? @db.VarChar(20)
- device String? @db.VarChar(20)
- screen String? @db.VarChar(11)
- language String? @db.VarChar(35)
- country String? @db.Char(2)
- website website? @relation(fields: [websiteId], references: [id])
- events event[]
- pageview pageview[]
+model Group {
+ id String @id() @unique() @map("group_id") @db.Uuid
+ name String @unique() @db.VarChar(255)
+ description String? @db.VarChar(255)
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
- @@index([createdAt])
- @@index([websiteId])
- @@index([sessionUuid])
+ groupRoles GroupRole[]
+ groupUsers GroupUser[]
+
+ @@map("group")
}
-model website {
- id Int @id @default(autoincrement()) @map("website_id")
- websiteUuid String @unique @map("website_uuid") @db.Uuid
- userId Int @map("user_id")
- name String @db.VarChar(100)
- domain String? @db.VarChar(500)
- shareId String? @unique @map("share_id") @db.VarChar(64)
- createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
- account account @relation(fields: [userId], references: [id])
- event event[]
- pageview pageview[]
- session session[]
+model GroupRole {
+ id String @id() @unique() @map("group_role_id") @db.Uuid
+ groupId String @map("group_id") @db.Uuid
+ roleId String @map("role_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
- @@index([userId])
- @@index([websiteUuid])
+ group Group @relation(fields: [groupId], references: [id])
+ role Role @relation(fields: [roleId], references: [id])
+ user User? @relation(fields: [userId], references: [id])
+ userId String? @db.Uuid
+
+ @@map("group_role")
+}
+
+model GroupUser {
+ id String @id() @unique() @map("group_user_id") @db.Uuid
+ groupId String @map("group_id") @db.Uuid
+ userId String @map("user_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ group Group @relation(fields: [groupId], references: [id])
+ User User @relation(fields: [userId], references: [id])
+
+ @@map("group_user")
+}
+
+model Permission {
+ id String @id() @unique() @map("permission_id") @db.Uuid
+ name String @unique() @db.VarChar(255)
+ description String? @db.VarChar(255)
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ @@map("permission")
+}
+
+model Role {
+ id String @id() @unique() @map("role_id") @db.Uuid
+ name String @unique() @db.VarChar(255)
+ description String? @db.VarChar(255)
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ groupRoles GroupRole[]
+ userRoles UserRole[]
+
+ @@map("role")
+}
+
+model UserRole {
+ id String @id() @unique() @map("user_role_id") @db.Uuid
+ roleId String @map("role_id") @db.Uuid
+ userId String @map("user_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ role Role @relation(fields: [roleId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@map("user_role")
+}
+
+model Team {
+ id String @id() @unique() @map("team_id") @db.Uuid
+ name String @unique() @db.VarChar(50)
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ teamWebsites TeamWebsite[]
+ teamUsers TeamUser[]
+
+ @@map("team")
+}
+
+model TeamWebsite {
+ id String @id() @unique() @map("team_website_id") @db.Uuid
+ teamId String @map("team_id") @db.Uuid
+ websiteId String @map("website_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ website Website @relation(fields: [websiteId], references: [id])
+ team Team @relation(fields: [teamId], references: [id])
+ user User? @relation(fields: [userId], references: [id])
+ userId String? @db.Uuid
+
+ @@map("team_website")
+}
+
+model TeamUser {
+ id String @id() @unique() @map("team_user_id") @db.Uuid
+ teamId String @map("team_id") @db.Uuid
+ userId String @map("user_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ isDeleted Boolean @default(false) @map("is_deleted")
+
+ team Team @relation(fields: [teamId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@map("team_user")
+}
+
+model UserWebsite {
+ id String @id() @unique() @map("user_website_id") @db.Uuid
+ userId String @map("user_id") @db.Uuid
+ websiteId String @map("website_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+
+ website Website @relation(fields: [websiteId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@map("user_website")
}
diff --git a/hooks/useConfig.js b/hooks/useConfig.js
index 8adedcaf..678b6d1c 100644
--- a/hooks/useConfig.js
+++ b/hooks/useConfig.js
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import useStore, { setConfig } from 'store/app';
import useApi from 'hooks/useApi';
-let fetched = false;
+let loading = false;
export default function useConfig() {
const { config } = useStore();
@@ -10,12 +10,13 @@ export default function useConfig() {
async function loadConfig() {
const { data } = await get('/config');
+ loading = false;
setConfig(data);
}
useEffect(() => {
- if (!config && !fetched) {
- fetched = true;
+ if (!config && !loading) {
+ loading = true;
loadConfig();
}
}, []);
diff --git a/hooks/useRequireLogin.js b/hooks/useRequireLogin.js
index b00f633b..b2edc347 100644
--- a/hooks/useRequireLogin.js
+++ b/hooks/useRequireLogin.js
@@ -12,14 +12,17 @@ export default function useRequireLogin() {
async function loadUser() {
setLoading(true);
- const { ok, data } = await get('/auth/verify');
+ const {
+ ok,
+ data: { user },
+ } = await get('/auth/verify');
if (!ok) {
await router.push('/login');
return null;
}
- setUser(data);
+ setUser(user);
setLoading(false);
}
diff --git a/interface/auth.d.ts b/interface/auth.d.ts
new file mode 100644
index 00000000..36d3041e
--- /dev/null
+++ b/interface/auth.d.ts
@@ -0,0 +1,5 @@
+export interface Auth {
+ id: number;
+ email?: string;
+ teams?: string[];
+}
diff --git a/interface/base.d.ts b/interface/base.d.ts
new file mode 100644
index 00000000..5d498ebf
--- /dev/null
+++ b/interface/base.d.ts
@@ -0,0 +1,22 @@
+import { NextApiRequest } from 'next';
+import { Auth } from './auth';
+
+export interface NextApiRequestQueryBody extends NextApiRequest {
+ auth: Auth;
+ query: TQuery;
+ body: TBody;
+}
+
+export interface NextApiRequestQuery extends NextApiRequest {
+ auth: Auth;
+ query: TQuery;
+}
+
+export interface NextApiRequestBody extends NextApiRequest {
+ auth: Auth;
+ body: TBody;
+}
+
+export interface ObjectAny {
+ [key: string]: any;
+}
diff --git a/interface/index.d.ts b/interface/index.d.ts
new file mode 100644
index 00000000..5d498ebf
--- /dev/null
+++ b/interface/index.d.ts
@@ -0,0 +1,22 @@
+import { NextApiRequest } from 'next';
+import { Auth } from './auth';
+
+export interface NextApiRequestQueryBody extends NextApiRequest {
+ auth: Auth;
+ query: TQuery;
+ body: TBody;
+}
+
+export interface NextApiRequestQuery extends NextApiRequest {
+ auth: Auth;
+ query: TQuery;
+}
+
+export interface NextApiRequestBody extends NextApiRequest {
+ auth: Auth;
+ body: TBody;
+}
+
+export interface ObjectAny {
+ [key: string]: any;
+}
diff --git a/lib/auth.js b/lib/auth.js
index 93027544..7a44f360 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -1,22 +1,31 @@
import { parseSecureToken, parseToken } from 'next-basics';
-import { getAccount, getWebsite } from 'queries';
-import { SHARE_TOKEN_HEADER, TYPE_ACCOUNT, TYPE_WEBSITE } from 'lib/constants';
+import { getUser, getWebsite } from 'queries';
+import debug from 'debug';
+import { SHARE_TOKEN_HEADER, TYPE_USER, TYPE_WEBSITE } from 'lib/constants';
import { secret } from 'lib/crypto';
-export function getAuthToken(req) {
- try {
- const token = req.headers.authorization;
+const log = debug('umami:auth');
- return parseSecureToken(token.split(' ')[1], secret());
- } catch {
+export function getAuthToken(req) {
+ const token = req.headers.authorization;
+
+ return token.split(' ')[1];
+}
+
+export function parseAuthToken(req) {
+ try {
+ return parseSecureToken(getAuthToken(req), secret());
+ } catch (e) {
+ log(e);
return null;
}
}
-export function getShareToken(req) {
+export function parseShareToken(req) {
try {
return parseToken(req.headers[SHARE_TOKEN_HEADER], secret());
- } catch {
+ } catch (e) {
+ log(e);
return null;
}
}
@@ -29,6 +38,7 @@ export function isValidToken(token, validation) {
return validation(token);
}
} catch (e) {
+ log(e);
return false;
}
@@ -38,7 +48,10 @@ export function isValidToken(token, validation) {
export async function allowQuery(req, type) {
const { id } = req.query;
- const { userId, isAdmin, shareToken } = req.auth ?? {};
+ const {
+ user: { id: userId, isAdmin },
+ shareToken,
+ } = req.auth;
if (isAdmin) {
return true;
@@ -50,13 +63,13 @@ export async function allowQuery(req, type) {
if (userId) {
if (type === TYPE_WEBSITE) {
- const website = await getWebsite({ websiteUuid: id });
+ const website = await getWebsite({ id });
return website && website.userId === userId;
- } else if (type === TYPE_ACCOUNT) {
- const account = await getAccount({ accountUuid: id });
+ } else if (type === TYPE_USER) {
+ const user = await getUser({ id });
- return account && account.accountUuid === id;
+ return user && user.id === id;
}
}
diff --git a/lib/cache.js b/lib/cache.js
new file mode 100644
index 00000000..bc065c14
--- /dev/null
+++ b/lib/cache.js
@@ -0,0 +1,84 @@
+import { getWebsite, getUser, getSession } from '../queries';
+import redis, { DELETED } from 'lib/redis';
+
+async function fetchObject(key, query) {
+ const obj = await redis.get(key);
+
+ if (!obj) {
+ return query().then(async data => {
+ if (data) {
+ await redis.set(key, data);
+ }
+
+ return data;
+ });
+ }
+
+ return obj;
+}
+
+async function storeObject(key, data) {
+ return redis.set(key, data);
+}
+
+async function deleteObject(key) {
+ return redis.set(key, DELETED);
+}
+
+async function fetchWebsite(id) {
+ return fetchObject(`website:${id}`, () => getWebsite({ id }));
+}
+
+async function storeWebsite(data) {
+ const { id } = data;
+ const key = `website:${id}`;
+
+ return storeObject(key, data);
+}
+
+async function deleteWebsite(id) {
+ return deleteObject(`website:${id}`);
+}
+
+async function fetchUser(id) {
+ return fetchObject(`user:${id}`, () => getUser({ id }));
+}
+
+async function storeUser(data) {
+ const { id } = data;
+ const key = `user:${id}`;
+
+ return storeObject(key, data);
+}
+
+async function deleteUser(id) {
+ return deleteObject(`user:${id}`);
+}
+
+async function fetchSession(id) {
+ return fetchObject(`session:${id}`, () => getSession({ id }));
+}
+
+async function storeSession(data) {
+ const { id } = data;
+ const key = `session:${id}`;
+
+ return storeObject(key, data);
+}
+
+async function deleteSession(id) {
+ return deleteObject(`session:${id}`);
+}
+
+export default {
+ fetchWebsite,
+ storeWebsite,
+ deleteWebsite,
+ fetchUser,
+ storeUser,
+ deleteUser,
+ fetchSession,
+ storeSession,
+ deleteSession,
+ enabled: redis.enabled,
+};
diff --git a/lib/constants.js b/lib/constants.js
index 8b3ee8d0..0e4eabec 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -22,7 +22,7 @@ export const REALTIME_RANGE = 30;
export const REALTIME_INTERVAL = 3000;
export const TYPE_WEBSITE = 'website';
-export const TYPE_ACCOUNT = 'account';
+export const TYPE_USER = 'user';
export const THEME_COLORS = {
light: {
diff --git a/lib/crypto.js b/lib/crypto.js
index 74470549..ceb8aa99 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -9,11 +9,11 @@ export function secret() {
export function salt() {
const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString());
- return hash([secret(), ROTATING_SALT]);
+ return hash(secret(), ROTATING_SALT);
}
export function uuid(...args) {
if (!args.length) return v4();
- return v5(hash([...args, salt()]), v5.DNS);
+ return v5(hash(...args, salt()), v5.DNS);
}
diff --git a/lib/middleware.js b/lib/middleware.js
index 8189ea66..7473e81c 100644
--- a/lib/middleware.js
+++ b/lib/middleware.js
@@ -1,23 +1,22 @@
-import { createMiddleware, unauthorized, badRequest, serverError } from 'next-basics';
+import { createMiddleware, unauthorized, badRequest, parseSecureToken } from 'next-basics';
+import debug from 'debug';
import cors from 'cors';
-import { getSession } from './session';
-import { getAuthToken, getShareToken } from './auth';
+import { validate } from 'uuid';
+import { findSession } from 'lib/session';
+import { parseShareToken, getAuthToken } from 'lib/auth';
+import { secret } from 'lib/crypto';
+import redis from 'lib/redis';
+import { getUser } from '../queries';
+
+const log = debug('umami:middleware');
export const useCors = createMiddleware(cors());
export const useSession = createMiddleware(async (req, res, next) => {
- let session;
-
- try {
- session = await getSession(req);
- } catch (e) {
- // eslint-disable-next-line no-console
- console.error(e);
-
- return serverError(res, e.message);
- }
+ const session = await findSession(req);
if (!session) {
+ log('useSession:session-not-found');
return badRequest(res);
}
@@ -26,13 +25,25 @@ export const useSession = createMiddleware(async (req, res, next) => {
});
export const useAuth = createMiddleware(async (req, res, next) => {
- const token = await getAuthToken(req);
- const shareToken = await getShareToken(req);
+ const token = getAuthToken(req);
+ const key = parseSecureToken(token, secret());
+ const shareToken = await parseShareToken(req);
- if (!token && !shareToken) {
+ let user;
+
+ if (validate(key)) {
+ user = await getUser({ id: key });
+ } else if (redis.enabled) {
+ user = await redis.get(key);
+ }
+
+ if (!user && !shareToken) {
+ log('useAuth:user-not-authorized');
return unauthorized(res);
}
- req.auth = { ...token, shareToken };
+ log({ user, token, shareToken, key });
+
+ req.auth = { user, token, shareToken, key };
next();
});
diff --git a/lib/redis.js b/lib/redis.js
index 85752c54..b2ea2279 100644
--- a/lib/redis.js
+++ b/lib/redis.js
@@ -1,18 +1,15 @@
-import Redis from 'ioredis';
-import { startOfMonth } from 'date-fns';
import debug from 'debug';
-import { getSessions, getAllWebsites } from 'queries';
+import Redis from 'ioredis';
import { REDIS } from 'lib/db';
const log = debug('umami:redis');
-const INITIALIZED = 'redis:initialized';
export const DELETED = 'deleted';
let redis;
const enabled = Boolean(process.env.REDIS_URL);
function getClient() {
- if (!process.env.REDIS_URL) {
+ if (!enabled) {
return null;
}
@@ -32,48 +29,34 @@ function getClient() {
return redis;
}
-async function stageData() {
- const sessions = await getSessions([], startOfMonth(new Date()));
- const websites = await getAllWebsites();
-
- const sessionUuids = sessions.map(a => {
- return { key: `session:${a.sessionUuid}`, value: 1 };
- });
- const websiteIds = websites.map(a => {
- return { key: `website:${a.websiteUuid}`, value: Number(a.websiteId) };
- });
-
- await addSet(sessionUuids);
- await addSet(websiteIds);
-
- await redis.set(INITIALIZED, 1);
-}
-
-async function addSet(ids) {
- for (let i = 0; i < ids.length; i++) {
- const { key, value } = ids[i];
- await redis.set(key, value);
- }
-}
-
async function get(key) {
await connect();
- return redis.get(key);
+ try {
+ return JSON.parse(await redis.get(key));
+ } catch {
+ return null;
+ }
}
async function set(key, value) {
await connect();
- return redis.set(key, value);
+ return redis.set(key, JSON.stringify(value));
+}
+
+async function del(key) {
+ await connect();
+
+ return redis.del(key);
}
async function connect() {
- if (!redis) {
- redis = process.env.REDIS_URL && (global[REDIS] || getClient());
+ if (!redis && enabled) {
+ redis = global[REDIS] || getClient();
}
return redis;
}
-export default { enabled, client: redis, log, connect, get, set, stageData };
+export default { enabled, client: redis, log, connect, get, set, del };
diff --git a/lib/session.js b/lib/session.js
index d1a88768..ad9f75b8 100644
--- a/lib/session.js
+++ b/lib/session.js
@@ -1,106 +1,96 @@
import { parseToken } from 'next-basics';
import { validate } from 'uuid';
import { secret, uuid } from 'lib/crypto';
-import redis, { DELETED } from 'lib/redis';
+import cache from 'lib/cache';
import clickhouse from 'lib/clickhouse';
import { getClientInfo, getJsonBody } from 'lib/request';
-import { createSession, getSessionByUuid, getWebsite } from 'queries';
+import { createSession, getSession, getWebsite } from 'queries';
-export async function getSession(req) {
+export async function findSession(req) {
const { payload } = getJsonBody(req);
if (!payload) {
- throw new Error('Invalid request');
+ return null;
}
- const cache = req.headers['x-umami-cache'];
+ // Check if cache token is passed
+ const cacheToken = req.headers['x-umami-cache'];
- if (cache) {
- const result = await parseToken(cache, secret());
+ if (cacheToken) {
+ const result = await parseToken(cacheToken, secret());
if (result) {
return result;
}
}
- const { website: websiteUuid, hostname, screen, language } = payload;
+ // Verify payload
+ const { website: websiteId, hostname, screen, language } = payload;
- if (!validate(websiteUuid)) {
+ if (!validate(websiteId)) {
return null;
}
- let websiteId = null;
+ // Find website
+ let website;
- // Check if website exists
- if (redis.enabled) {
- websiteId = Number(await redis.get(`website:${websiteUuid}`));
+ if (cache.enabled) {
+ website = await cache.fetchWebsite(websiteId);
+ } else {
+ website = await getWebsite({ id: websiteId });
}
- // Check database if does not exists in Redis
- if (!websiteId) {
- const website = await getWebsite({ websiteUuid });
- websiteId = website ? website.id : null;
- }
-
- if (!websiteId || websiteId === DELETED) {
- throw new Error(`Website not found: ${websiteUuid}`);
+ if (!website || website.isDeleted) {
+ throw new Error(`Website not found: ${websiteId}`);
}
const { userAgent, browser, os, ip, country, device } = await getClientInfo(req, payload);
- const sessionUuid = uuid(websiteUuid, hostname, ip, userAgent);
+ const sessionId = uuid(websiteId, hostname, ip, userAgent);
- let sessionId = null;
- let session = null;
-
- if (!clickhouse.enabled) {
- // Check if session exists
- if (redis.enabled) {
- sessionId = Number(await redis.get(`session:${sessionUuid}`));
- }
-
- // Check database if does not exists in Redis
- if (!sessionId) {
- session = await getSessionByUuid(sessionUuid);
- sessionId = session ? session.id : null;
- }
-
- if (!sessionId) {
- try {
- session = await createSession(websiteId, {
- sessionUuid,
- hostname,
- browser,
- os,
- screen,
- language,
- country,
- device,
- });
- } catch (e) {
- if (!e.message.toLowerCase().includes('unique constraint')) {
- throw e;
- }
- }
- }
- } else {
- session = {
- sessionId,
- sessionUuid,
+ // Clickhouse does not require session lookup
+ if (clickhouse.enabled) {
+ return {
+ id: sessionId,
+ websiteId,
hostname,
browser,
os,
+ device,
screen,
language,
country,
- device,
};
}
- return {
- website: {
- websiteId,
- websiteUuid,
- },
- session,
- };
+ // Find session
+ let session;
+
+ if (cache.enabled) {
+ session = await cache.fetchSession(sessionId);
+ } else {
+ session = await getSession({ id: sessionId });
+ }
+
+ // Create a session if not found
+ if (!session) {
+ try {
+ session = await createSession({
+ id: sessionId,
+ websiteId,
+ hostname,
+ browser,
+ os,
+ device,
+ screen,
+ language,
+ country,
+ });
+ } catch (e) {
+ if (!e.message.toLowerCase().includes('unique constraint')) {
+ throw e;
+ }
+ }
+ }
+
+ return session;
}
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644
index 00000000..62b8a52d
--- /dev/null
+++ b/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/next.config.js b/next.config.js
index e26d882e..b95c39d4 100644
--- a/next.config.js
+++ b/next.config.js
@@ -36,7 +36,7 @@ module.exports = {
env: {
currentVersion: pkg.version,
isProduction: process.env.NODE_ENV === 'production',
- isCloudMode: process.env.CLOUD_MODE,
+ uiDisabled: !!process.env.DISABLE_UI,
},
basePath: process.env.BASE_PATH,
output: 'standalone',
diff --git a/package.json b/package.json
index 5b906819..f3b4304f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "1.39.4",
+ "version": "2.0.0-beta.2",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao ",
"license": "MIT",
@@ -84,7 +84,7 @@
"maxmind": "^4.3.6",
"moment-timezone": "^0.5.35",
"next": "^12.3.1",
- "next-basics": "^0.18.0",
+ "next-basics": "^0.23.0",
"node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5",
"prop-types": "^15.7.2",
diff --git a/pages/_app.js b/pages/_app.js
index b8653113..c3617a08 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -2,30 +2,27 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import { IntlProvider } from 'react-intl';
import useLocale from 'hooks/useLocale';
+import useConfig from 'hooks/useConfig';
import 'styles/variables.css';
import 'styles/bootstrap-grid.css';
import 'styles/index.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/600.css';
-const Intl = ({ children }) => {
+export default function App({ Component, pageProps }) {
const { locale, messages } = useLocale();
+ const { basePath } = useRouter();
+ const { dir } = useLocale();
+ useConfig();
const Wrapper = ({ children }) => {children};
+ if (process.env.uiDisabled) {
+ return null;
+ }
+
return (
- {children}
-
- );
-};
-
-export default function App({ Component, pageProps }) {
- const { basePath } = useRouter();
- const { dir } = useLocale();
-
- return (
-
@@ -41,6 +38,6 @@ export default function App({ Component, pageProps }) {
-
+
);
}
diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js
index aa4803d8..a54e8013 100644
--- a/pages/api/auth/login.js
+++ b/pages/api/auth/login.js
@@ -1,23 +1,44 @@
-import { ok, unauthorized, badRequest, checkPassword, createSecureToken } from 'next-basics';
-import { getAccount } from 'queries';
+import {
+ ok,
+ unauthorized,
+ badRequest,
+ checkPassword,
+ createSecureToken,
+ methodNotAllowed,
+ getRandomChars,
+} from 'next-basics';
+import { getUser } from 'queries';
import { secret } from 'lib/crypto';
+import redis from 'lib/redis';
export default async (req, res) => {
- const { username, password } = req.body;
+ if (req.method === 'POST') {
+ const { username, password } = req.body;
- if (!username || !password) {
- return badRequest(res);
+ if (!username || !password) {
+ return badRequest(res);
+ }
+
+ const user = await getUser({ username });
+
+ if (user && checkPassword(password, user.password)) {
+ if (redis.enabled) {
+ const key = `auth:${getRandomChars(32)}`;
+
+ await redis.set(key, user);
+
+ const token = createSecureToken(key, secret());
+
+ return ok(res, { token, user });
+ }
+
+ const token = createSecureToken(user.id, secret());
+
+ return ok(res, { token, user });
+ }
+
+ return unauthorized(res, 'Incorrect username and/or password.');
}
- const account = await getAccount({ username });
-
- if (account && checkPassword(password, account.password)) {
- const { id, username, isAdmin, accountUuid } = account;
- const user = { userId: id, username, isAdmin, accountUuid };
- const token = createSecureToken(user, secret());
-
- return ok(res, { token, user });
- }
-
- return unauthorized(res);
+ return methodNotAllowed(res);
};
diff --git a/pages/api/auth/logout.js b/pages/api/auth/logout.js
new file mode 100644
index 00000000..37f117be
--- /dev/null
+++ b/pages/api/auth/logout.js
@@ -0,0 +1,18 @@
+import { methodNotAllowed, ok } from 'next-basics';
+import { useAuth } from 'lib/middleware';
+import redis from 'lib/redis';
+import { getAuthToken } from 'lib/auth';
+
+export default async (req, res) => {
+ await useAuth(req, res);
+
+ if (req.method === 'POST') {
+ if (redis.enabled) {
+ await redis.del(getAuthToken(req));
+ }
+
+ return ok(res);
+ }
+
+ return methodNotAllowed(res);
+};
diff --git a/pages/api/auth/verify.js b/pages/api/auth/verify.js
index 303a38ec..670480b1 100644
--- a/pages/api/auth/verify.js
+++ b/pages/api/auth/verify.js
@@ -1,12 +1,8 @@
import { useAuth } from 'lib/middleware';
-import { ok, unauthorized } from 'next-basics';
+import { ok } from 'next-basics';
export default async (req, res) => {
await useAuth(req, res);
- if (req.auth) {
- return ok(res, req.auth);
- }
-
- return unauthorized(res);
+ return ok(res, req.auth);
};
diff --git a/pages/api/collect.js b/pages/api/collect.js
index 5a4411d5..87e516cc 100644
--- a/pages/api/collect.js
+++ b/pages/api/collect.js
@@ -5,7 +5,7 @@ import { createToken, unauthorized, send, badRequest, forbidden } from 'next-bas
import { savePageView, saveEvent } from 'queries';
import { useCors, useSession } from 'lib/middleware';
import { getJsonBody, getIpAddress } from 'lib/request';
-import { secret, uuid } from 'lib/crypto';
+import { secret } from 'lib/crypto';
export default async (req, res) => {
await useCors(req, res);
@@ -58,7 +58,7 @@ export default async (req, res) => {
await useSession(req, res);
- const { website, session } = req.session;
+ const session = req.session;
const { type, payload } = getJsonBody(req);
@@ -68,14 +68,11 @@ export default async (req, res) => {
url = url.replace(/\/$/, '');
}
- const eventUuid = uuid();
-
if (type === 'pageview') {
- await savePageView(website, { session, url, referrer });
+ await savePageView({ ...session, url, referrer });
} else if (type === 'event') {
- await saveEvent(website, {
- session,
- eventUuid,
+ await saveEvent({
+ ...session,
url,
eventName,
eventData,
@@ -84,13 +81,7 @@ export default async (req, res) => {
return badRequest(res);
}
- const token = createToken(
- {
- website,
- session,
- },
- secret(),
- );
+ const token = createToken(session, secret());
return send(res, token);
};
diff --git a/pages/api/config.js b/pages/api/config.js
index 27a04eaa..faf94c0b 100644
--- a/pages/api/config.js
+++ b/pages/api/config.js
@@ -7,6 +7,7 @@ export default async (req, res) => {
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
updatesDisabled: !!process.env.DISABLE_UPDATES,
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
+ adminDisabled: !!process.env.DISABLE_ADMIN,
});
}
diff --git a/pages/api/realtime/init.js b/pages/api/realtime/init.js
index 9a9a4297..16e7cad3 100644
--- a/pages/api/realtime/init.js
+++ b/pages/api/realtime/init.js
@@ -8,10 +8,10 @@ export default async (req, res) => {
await useAuth(req, res);
if (req.method === 'GET') {
- const { userId } = req.auth;
+ const { id: userId } = req.auth.user;
- const websites = await getUserWebsites({ userId });
- const ids = websites.map(({ websiteUuid }) => websiteUuid);
+ const websites = await getUserWebsites(userId);
+ const ids = websites.map(({ id }) => id);
const token = createToken({ websites: ids }, secret());
const data = await getRealtimeData(ids, subMinutes(new Date(), 30));
diff --git a/pages/api/share/[id].js b/pages/api/share/[id].js
index 4ff6b81c..6fb19739 100644
--- a/pages/api/share/[id].js
+++ b/pages/api/share/[id].js
@@ -3,14 +3,14 @@ import { ok, notFound, methodNotAllowed, createToken } from 'next-basics';
import { secret } from 'lib/crypto';
export default async (req, res) => {
- const { id } = req.query;
+ const { id: shareId } = req.query;
if (req.method === 'GET') {
- const website = await getWebsite({ shareId: id });
+ const website = await getWebsite({ shareId });
if (website) {
- const { websiteUuid } = website;
- const data = { id: websiteUuid };
+ const { id } = website;
+ const data = { id };
const token = createToken(data, secret());
return ok(res, { ...data, token });
diff --git a/pages/api/accounts/[id]/index.js b/pages/api/users/[id]/index.js
similarity index 60%
rename from pages/api/accounts/[id]/index.js
rename to pages/api/users/[id]/index.js
index c5f16d4e..a373bbd1 100644
--- a/pages/api/accounts/[id]/index.js
+++ b/pages/api/users/[id]/index.js
@@ -1,11 +1,13 @@
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
-import { getAccount, deleteAccount, updateAccount } from 'queries';
+import { getUser, deleteUser, updateUser } from 'queries';
import { useAuth } from 'lib/middleware';
export default async (req, res) => {
await useAuth(req, res);
- const { isAdmin, userId } = req.auth;
+ const {
+ user: { id: userId, isAdmin },
+ } = req.auth;
const { id } = req.query;
if (req.method === 'GET') {
@@ -13,9 +15,9 @@ export default async (req, res) => {
return unauthorized(res);
}
- const account = await getAccount({ id: +id });
+ const user = await getUser({ id });
- return ok(res, account);
+ return ok(res, user);
}
if (req.method === 'POST') {
@@ -25,7 +27,7 @@ export default async (req, res) => {
return unauthorized(res);
}
- const account = await getAccount({ id: +id });
+ const user = await getUser({ id });
const data = {};
@@ -39,29 +41,29 @@ export default async (req, res) => {
}
// Check when username changes
- if (data.username && account.username !== data.username) {
- const accountByUsername = await getAccount({ username });
+ if (data.username && user.username !== data.username) {
+ const userByUsername = await getUser({ username });
- if (accountByUsername) {
- return badRequest(res, 'Account already exists.');
+ if (userByUsername) {
+ return badRequest(res, 'User already exists');
}
}
- const updated = await updateAccount(data, { id: +id });
+ const updated = await updateUser(data, { id });
return ok(res, updated);
}
if (req.method === 'DELETE') {
if (id === userId) {
- return badRequest(res, 'You cannot delete your own account.');
+ return badRequest(res, 'You cannot delete your own user.');
}
if (!isAdmin) {
return unauthorized(res);
}
- await deleteAccount(+id);
+ await deleteUser(id);
return ok(res);
}
diff --git a/pages/api/accounts/[id]/password.js b/pages/api/users/[id]/password.js
similarity index 60%
rename from pages/api/accounts/[id]/password.js
rename to pages/api/users/[id]/password.js
index 89649c20..6cad82ed 100644
--- a/pages/api/accounts/[id]/password.js
+++ b/pages/api/users/[id]/password.js
@@ -1,4 +1,4 @@
-import { getAccount, updateAccount } from 'queries';
+import { getUser, updateUser } from 'queries';
import { useAuth } from 'lib/middleware';
import {
badRequest,
@@ -9,28 +9,28 @@ import {
hashPassword,
} from 'next-basics';
import { allowQuery } from 'lib/auth';
-import { TYPE_ACCOUNT } from 'lib/constants';
+import { TYPE_USER } from 'lib/constants';
export default async (req, res) => {
await useAuth(req, res);
const { current_password, new_password } = req.body;
- const { id: accountUuid } = req.query;
+ const { id } = req.query;
- if (!(await allowQuery(req, TYPE_ACCOUNT))) {
+ if (!(await allowQuery(req, TYPE_USER))) {
return unauthorized(res);
}
if (req.method === 'POST') {
- const account = await getAccount({ accountUuid });
+ const user = await getUser({ id });
- if (!checkPassword(current_password, account.password)) {
+ if (!checkPassword(current_password, user.password)) {
return badRequest(res, 'Current password is incorrect');
}
const password = hashPassword(new_password);
- const updated = await updateAccount({ password }, { accountUuid });
+ const updated = await updateUser({ password }, { id });
return ok(res, updated);
}
diff --git a/pages/api/accounts/index.js b/pages/api/users/index.js
similarity index 53%
rename from pages/api/accounts/index.js
rename to pages/api/users/index.js
index 22248ed4..f4a5010a 100644
--- a/pages/api/accounts/index.js
+++ b/pages/api/users/index.js
@@ -1,36 +1,38 @@
import { ok, unauthorized, methodNotAllowed, badRequest, hashPassword } from 'next-basics';
import { useAuth } from 'lib/middleware';
import { uuid } from 'lib/crypto';
-import { createAccount, getAccount, getAccounts } from 'queries';
+import { createUser, getUser, getUsers } from 'queries';
export default async (req, res) => {
await useAuth(req, res);
- const { isAdmin } = req.auth;
+ const {
+ user: { isAdmin },
+ } = req.auth;
if (!isAdmin) {
return unauthorized(res);
}
if (req.method === 'GET') {
- const accounts = await getAccounts();
+ const users = await getUsers();
- return ok(res, accounts);
+ return ok(res, users);
}
if (req.method === 'POST') {
- const { username, password, account_uuid } = req.body;
+ const { username, password, id } = req.body;
- const account = await getAccount({ username });
+ const user = await getUser({ username });
- if (account) {
- return badRequest(res, 'Account already exists');
+ if (user) {
+ return badRequest(res, 'User already exists');
}
- const created = await createAccount({
+ const created = await createUser({
+ id: id || uuid(),
username,
password: hashPassword(password),
- accountUuid: account_uuid || uuid(),
});
return ok(res, created);
diff --git a/pages/api/websites/[id]/index.js b/pages/api/websites/[id]/index.js
index a3771427..09056865 100644
--- a/pages/api/websites/[id]/index.js
+++ b/pages/api/websites/[id]/index.js
@@ -1,53 +1,34 @@
import { allowQuery } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
-import { getRandomChars, methodNotAllowed, ok, serverError, unauthorized } from 'next-basics';
-import { deleteWebsite, getAccount, getWebsite, updateWebsite } from 'queries';
+import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics';
+import { deleteWebsite, getWebsite, updateWebsite } from 'queries';
import { TYPE_WEBSITE } from 'lib/constants';
export default async (req, res) => {
await useCors(req, res);
await useAuth(req, res);
- const { id: websiteUuid } = req.query;
+ const { id: websiteId } = req.query;
if (!(await allowQuery(req, TYPE_WEBSITE))) {
return unauthorized(res);
}
if (req.method === 'GET') {
- const website = await getWebsite({ websiteUuid });
+ const website = await getWebsite({ id: websiteId });
return ok(res, website);
}
if (req.method === 'POST') {
- const { name, domain, owner, enableShareUrl, shareId } = req.body;
- const { accountUuid } = req.auth;
-
- let account;
-
- if (accountUuid) {
- account = await getAccount({ accountUuid });
-
- if (!account) {
- return serverError(res, 'Account does not exist.');
- }
- }
-
- const website = await getWebsite({ websiteUuid });
-
- const newShareId = enableShareUrl ? website.shareId || getRandomChars(8) : null;
+ const { name, domain, shareId } = req.body;
try {
- await updateWebsite(
- {
- name,
- domain,
- shareId: shareId ? shareId : newShareId,
- userId: +owner || account.id,
- },
- { websiteUuid },
- );
+ await updateWebsite(websiteId, {
+ name,
+ domain,
+ shareId,
+ });
} catch (e) {
if (e.message.includes('Unique constraint') && e.message.includes('share_id')) {
return serverError(res, 'That share ID is already taken.');
@@ -62,7 +43,7 @@ export default async (req, res) => {
return unauthorized(res);
}
- await deleteWebsite(websiteUuid);
+ await deleteWebsite(websiteId);
return ok(res);
}
diff --git a/pages/api/websites/[id]/metrics.js b/pages/api/websites/[id]/metrics.js
index 0aafe7d3..97216273 100644
--- a/pages/api/websites/[id]/metrics.js
+++ b/pages/api/websites/[id]/metrics.js
@@ -94,7 +94,7 @@ export default async (req, res) => {
let domain;
if (type === 'referrer') {
- const website = await getWebsite({ websiteUuid: websiteId });
+ const website = await getWebsite({ id: websiteId });
if (!website) {
return badRequest(res);
diff --git a/pages/api/websites/index.js b/pages/api/websites/index.js
index daecac88..3966ad70 100644
--- a/pages/api/websites/index.js
+++ b/pages/api/websites/index.js
@@ -1,47 +1,30 @@
-import { createWebsite, getAccount, getAllWebsites, getUserWebsites } from 'queries';
-import { ok, methodNotAllowed, unauthorized, getRandomChars } from 'next-basics';
-import { useAuth } from 'lib/middleware';
+import { createWebsite, getAllWebsites, getUserWebsites } from 'queries';
+import { ok, methodNotAllowed, getRandomChars } from 'next-basics';
+import { useAuth, useCors } from 'lib/middleware';
import { uuid } from 'lib/crypto';
export default async (req, res) => {
+ await useCors(req, res);
await useAuth(req, res);
- const { user_id, include_all } = req.query;
- const { userId: currentUserId, isAdmin } = req.auth;
- const accountUuid = user_id || req.auth.accountUuid;
- let account;
-
- if (accountUuid) {
- account = await getAccount({ accountUuid });
- }
-
- const userId = account ? account.id : user_id;
+ const {
+ user: { id: userId, isAdmin },
+ } = req.auth;
if (req.method === 'GET') {
- if (userId && userId !== currentUserId && !isAdmin) {
- return unauthorized(res);
- }
+ const { include_all } = req.query;
const websites =
- isAdmin && include_all
- ? await getAllWebsites()
- : await getUserWebsites({ userId: account?.id });
+ isAdmin && include_all ? await getAllWebsites() : await getUserWebsites(userId);
return ok(res, websites);
}
if (req.method === 'POST') {
- const { name, domain, owner, enableShareUrl } = req.body;
+ const { name, domain, enableShareUrl } = req.body;
- const website_owner = account ? account.id : +owner;
-
- if (website_owner !== currentUserId && !isAdmin) {
- return unauthorized(res);
- }
-
- const websiteUuid = uuid();
const shareId = enableShareUrl ? getRandomChars(8) : null;
- const website = await createWebsite(website_owner, { websiteUuid, name, domain, shareId });
+ const website = await createWebsite(userId, { id: uuid(), name, domain, shareId });
return ok(res, website);
}
diff --git a/pages/console/[[...id]].js b/pages/console/[[...id]].js
index a13537f8..270a81df 100644
--- a/pages/console/[[...id]].js
+++ b/pages/console/[[...id]].js
@@ -4,11 +4,11 @@ import TestConsole from 'components/pages/TestConsole';
import useRequireLogin from 'hooks/useRequireLogin';
import useUser from 'hooks/useUser';
-export default function ConsolePage({ enabled }) {
+export default function ConsolePage({ pageDisabled }) {
const { loading } = useRequireLogin();
const { user } = useUser();
- if (loading || !enabled || !user?.isAdmin) {
+ if (pageDisabled || loading || !user?.isAdmin) {
return null;
}
@@ -21,6 +21,8 @@ export default function ConsolePage({ enabled }) {
export async function getServerSideProps() {
return {
- props: { enabled: !!process.env.ENABLE_TEST_CONSOLE },
+ props: {
+ pageDisabled: !process.env.ENABLE_TEST_CONSOLE,
+ },
};
}
diff --git a/pages/dashboard/[[...id]].js b/pages/dashboard/[[...id]].js
index 7c762097..c9ef6231 100644
--- a/pages/dashboard/[[...id]].js
+++ b/pages/dashboard/[[...id]].js
@@ -1,9 +1,10 @@
import React from 'react';
+import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
import Dashboard from 'components/pages/Dashboard';
import useRequireLogin from 'hooks/useRequireLogin';
-import { useRouter } from 'next/router';
import useUser from 'hooks/useUser';
+import useConfig from 'hooks/useConfig';
export default function DashboardPage() {
const {
@@ -13,8 +14,9 @@ export default function DashboardPage() {
} = useRouter();
const { loading } = useRequireLogin();
const user = useUser();
+ const { adminDisabled } = useConfig();
- if (!user || !isReady || loading) {
+ if (adminDisabled || !user || !isReady || loading) {
return null;
}
diff --git a/pages/login.js b/pages/login.js
index 12f70ac9..fb2fcb21 100644
--- a/pages/login.js
+++ b/pages/login.js
@@ -2,8 +2,8 @@ import React from 'react';
import Layout from 'components/layout/Layout';
import LoginForm from 'components/forms/LoginForm';
-export default function LoginPage({ loginDisabled }) {
- if (loginDisabled) {
+export default function LoginPage({ pageDisabled }) {
+ if (pageDisabled) {
return null;
}
@@ -16,6 +16,8 @@ export default function LoginPage({ loginDisabled }) {
export async function getServerSideProps() {
return {
- props: { loginDisabled: !!process.env.DISABLE_LOGIN || !!process.env.isCloudMode },
+ props: {
+ pageDisabled: !!process.env.DISABLE_LOGIN,
+ },
};
}
diff --git a/pages/logout.js b/pages/logout.js
index bcc99a10..a91205a4 100644
--- a/pages/logout.js
+++ b/pages/logout.js
@@ -1,14 +1,22 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
-import { removeItem } from 'next-basics';
+import { removeItem, useApi } from 'next-basics';
import { AUTH_TOKEN } from 'lib/constants';
import { setUser } from 'store/app';
export default function LogoutPage() {
const router = useRouter();
+ const { post } = useApi();
useEffect(() => {
+ async function logout() {
+ await post('/logout');
+ }
+
removeItem(AUTH_TOKEN);
+
+ logout();
+
router.push('/login');
return () => setUser(null);
diff --git a/pages/settings/index.js b/pages/settings/index.js
index 18f33109..02e89cea 100644
--- a/pages/settings/index.js
+++ b/pages/settings/index.js
@@ -2,11 +2,13 @@ import React from 'react';
import Layout from 'components/layout/Layout';
import Settings from 'components/pages/Settings';
import useRequireLogin from 'hooks/useRequireLogin';
+import useConfig from 'hooks/useConfig';
export default function SettingsPage() {
const { loading } = useRequireLogin();
+ const { adminDisabled } = useConfig();
- if (process.env.isCloudMode || loading) {
+ if (adminDisabled || loading) {
return null;
}
diff --git a/pages/settings/accounts.js b/pages/settings/users.js
similarity index 100%
rename from pages/settings/accounts.js
rename to pages/settings/users.js
diff --git a/pages/sso.js b/pages/sso.js
new file mode 100644
index 00000000..17ffa6b8
--- /dev/null
+++ b/pages/sso.js
@@ -0,0 +1,19 @@
+import { useEffect } from 'react';
+import { useRouter } from 'next/router';
+import { setItem } from 'next-basics';
+import { AUTH_TOKEN } from 'lib/constants';
+
+export default function SingleSignOnPage() {
+ const router = useRouter();
+ const { token, url } = router.query;
+
+ useEffect(() => {
+ if (url && token) {
+ setItem(AUTH_TOKEN, token);
+
+ router.push(url);
+ }
+ }, [router, url, token]);
+
+ return null;
+}
diff --git a/queries/admin/account/createAccount.js b/queries/admin/account/createAccount.js
deleted file mode 100644
index 2c4e1a40..00000000
--- a/queries/admin/account/createAccount.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import prisma from 'lib/prisma';
-
-export async function createAccount(data) {
- return prisma.client.account.create({
- data,
- });
-}
diff --git a/queries/admin/account/deleteAccount.js b/queries/admin/account/deleteAccount.js
deleted file mode 100644
index 9f6dd636..00000000
--- a/queries/admin/account/deleteAccount.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import prisma from 'lib/prisma';
-import redis, { DELETED } from 'lib/redis';
-
-export async function deleteAccount(userId) {
- const { client } = prisma;
-
- const websites = await client.website.findMany({
- where: { userId },
- select: { websiteUuid: true },
- });
-
- let websiteUuids = [];
-
- if (websites.length > 0) {
- websiteUuids = websites.map(a => a.websiteUuid);
- }
-
- return client
- .$transaction([
- client.pageview.deleteMany({
- where: { session: { website: { userId } } },
- }),
- client.eventData.deleteMany({
- where: { event: { session: { website: { userId } } } },
- }),
- client.event.deleteMany({
- where: { session: { website: { userId } } },
- }),
- client.session.deleteMany({
- where: { website: { userId } },
- }),
- client.website.deleteMany({
- where: { userId },
- }),
- client.account.delete({
- where: {
- id: userId,
- },
- }),
- ])
- .then(async res => {
- if (redis.enabled) {
- for (let i = 0; i < websiteUuids.length; i++) {
- await redis.set(`website:${websiteUuids[i]}`, DELETED);
- }
- }
-
- return res;
- });
-}
diff --git a/queries/admin/account/getAccount.js b/queries/admin/account/getAccount.js
deleted file mode 100644
index c414f56d..00000000
--- a/queries/admin/account/getAccount.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import prisma from 'lib/prisma';
-
-export async function getAccount(where) {
- return prisma.client.account.findUnique({
- where,
- });
-}
diff --git a/queries/admin/account/updateAccount.js b/queries/admin/account/updateAccount.js
deleted file mode 100644
index 3a1cadca..00000000
--- a/queries/admin/account/updateAccount.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import prisma from 'lib/prisma';
-
-export async function updateAccount(data, where) {
- return prisma.client.account.update({
- where,
- data,
- });
-}
diff --git a/queries/admin/user/createUser.js b/queries/admin/user/createUser.js
new file mode 100644
index 00000000..54e008fe
--- /dev/null
+++ b/queries/admin/user/createUser.js
@@ -0,0 +1,7 @@
+import prisma from 'lib/prisma';
+
+export async function createUser(data) {
+ return prisma.client.user.create({
+ data,
+ });
+}
diff --git a/queries/admin/user/deleteUser.js b/queries/admin/user/deleteUser.js
new file mode 100644
index 00000000..5970d2a5
--- /dev/null
+++ b/queries/admin/user/deleteUser.js
@@ -0,0 +1,45 @@
+import prisma from 'lib/prisma';
+import cache from 'lib/cache';
+
+export async function deleteUser(userId) {
+ const { client } = prisma;
+
+ const websites = await client.website.findMany({
+ where: { userId },
+ });
+
+ let websiteIds = [];
+
+ if (websites.length > 0) {
+ websiteIds = websites.map(a => a.id);
+ }
+
+ return client
+ .$transaction([
+ client.websiteEvent.deleteMany({
+ where: { websiteId: { in: websiteIds } },
+ }),
+ client.session.deleteMany({
+ where: { websiteId: { in: websiteIds } },
+ }),
+ client.website.deleteMany({
+ where: { userId },
+ }),
+ client.user.delete({
+ where: {
+ id: userId,
+ },
+ }),
+ ])
+ .then(async data => {
+ if (cache.enabled) {
+ const ids = websites.map(a => a.id);
+
+ for (let i = 0; i < ids.length; i++) {
+ await cache.deleteWebsite(`website:${ids[i]}`);
+ }
+ }
+
+ return data;
+ });
+}
diff --git a/queries/admin/user/getUser.js b/queries/admin/user/getUser.js
new file mode 100644
index 00000000..6c9ebd88
--- /dev/null
+++ b/queries/admin/user/getUser.js
@@ -0,0 +1,7 @@
+import prisma from 'lib/prisma';
+
+export async function getUser(where) {
+ return prisma.client.user.findUnique({
+ where,
+ });
+}
diff --git a/queries/admin/account/getAccounts.js b/queries/admin/user/getUsers.js
similarity index 64%
rename from queries/admin/account/getAccounts.js
rename to queries/admin/user/getUsers.js
index ceca2582..2fc473cb 100644
--- a/queries/admin/account/getAccounts.js
+++ b/queries/admin/user/getUsers.js
@@ -1,7 +1,7 @@
import prisma from 'lib/prisma';
-export async function getAccounts() {
- return prisma.client.account.findMany({
+export async function getUsers() {
+ return prisma.client.user.findMany({
orderBy: [
{ isAdmin: 'desc' },
{
@@ -13,8 +13,6 @@ export async function getAccounts() {
username: true,
isAdmin: true,
createdAt: true,
- updatedAt: true,
- accountUuid: true,
},
});
}
diff --git a/queries/admin/user/updateUser.js b/queries/admin/user/updateUser.js
new file mode 100644
index 00000000..ea80cf43
--- /dev/null
+++ b/queries/admin/user/updateUser.js
@@ -0,0 +1,8 @@
+import prisma from 'lib/prisma';
+
+export async function updateUser(data, where) {
+ return prisma.client.user.update({
+ where,
+ data,
+ });
+}
diff --git a/queries/admin/website/createWebsite.js b/queries/admin/website/createWebsite.js
index 22ac50c6..0afe0bea 100644
--- a/queries/admin/website/createWebsite.js
+++ b/queries/admin/website/createWebsite.js
@@ -1,11 +1,11 @@
import prisma from 'lib/prisma';
-import redis from 'lib/redis';
+import cache from 'lib/cache';
export async function createWebsite(userId, data) {
return prisma.client.website
.create({
data: {
- account: {
+ user: {
connect: {
id: userId,
},
@@ -13,11 +13,11 @@ export async function createWebsite(userId, data) {
...data,
},
})
- .then(async res => {
- if (redis.enabled && res) {
- await redis.set(`website:${res.websiteUuid}`, res.id);
+ .then(async data => {
+ if (cache.enabled) {
+ await cache.storeWebsite(data);
}
- return res;
+ return data;
});
}
diff --git a/queries/admin/website/deleteWebsite.js b/queries/admin/website/deleteWebsite.js
index 4eb4d97b..685cee8a 100644
--- a/queries/admin/website/deleteWebsite.js
+++ b/queries/admin/website/deleteWebsite.js
@@ -1,30 +1,24 @@
import prisma from 'lib/prisma';
-import redis, { DELETED } from 'lib/redis';
+import cache from 'lib/cache';
-export async function deleteWebsite(websiteUuid) {
+export async function deleteWebsite(id) {
const { client, transaction } = prisma;
return transaction([
- client.pageview.deleteMany({
- where: { session: { website: { websiteUuid } } },
- }),
- client.eventData.deleteMany({
- where: { event: { session: { website: { websiteUuid } } } },
- }),
- client.event.deleteMany({
- where: { session: { website: { websiteUuid } } },
+ client.websiteEvent.deleteMany({
+ where: { websiteId: id },
}),
client.session.deleteMany({
- where: { website: { websiteUuid } },
+ where: { websiteId: id },
}),
client.website.delete({
- where: { websiteUuid },
+ where: { id },
}),
- ]).then(async res => {
- if (redis.enabled) {
- await redis.set(`website:${websiteUuid}`, DELETED);
+ ]).then(async data => {
+ if (cache.enabled) {
+ await cache.deleteWebsite(id);
}
- return res;
+ return data;
});
}
diff --git a/queries/admin/website/getAllWebsites.js b/queries/admin/website/getAllWebsites.js
index 2c2c1bee..f9ad262c 100644
--- a/queries/admin/website/getAllWebsites.js
+++ b/queries/admin/website/getAllWebsites.js
@@ -11,7 +11,7 @@ export async function getAllWebsites() {
},
],
include: {
- account: {
+ user: {
select: {
username: true,
},
@@ -19,5 +19,5 @@ export async function getAllWebsites() {
},
});
- return data.map(i => ({ ...i, account: i.account.username }));
+ return data.map(i => ({ ...i, user: i.user.username }));
}
diff --git a/queries/admin/website/getUserWebsites.js b/queries/admin/website/getUserWebsites.js
index 9a725ec2..c1a9d559 100644
--- a/queries/admin/website/getUserWebsites.js
+++ b/queries/admin/website/getUserWebsites.js
@@ -1,8 +1,10 @@
import prisma from 'lib/prisma';
-export async function getUserWebsites(where) {
+export async function getUserWebsites(userId) {
return prisma.client.website.findMany({
- where,
+ where: {
+ userId,
+ },
orderBy: {
name: 'asc',
},
diff --git a/queries/admin/website/getWebsite.js b/queries/admin/website/getWebsite.js
index 6c109ff7..83c3e83a 100644
--- a/queries/admin/website/getWebsite.js
+++ b/queries/admin/website/getWebsite.js
@@ -1,16 +1,7 @@
import prisma from 'lib/prisma';
-import redis from 'lib/redis';
export async function getWebsite(where) {
- return prisma.client.website
- .findUnique({
- where,
- })
- .then(async data => {
- if (redis.enabled && data) {
- await redis.set(`website:${data.websiteUuid}`, data.id);
- }
-
- return data;
- });
+ return prisma.client.website.findUnique({
+ where,
+ });
}
diff --git a/queries/admin/website/resetWebsite.js b/queries/admin/website/resetWebsite.js
index d969b0d0..f4d685cb 100644
--- a/queries/admin/website/resetWebsite.js
+++ b/queries/admin/website/resetWebsite.js
@@ -1,20 +1,25 @@
import prisma from 'lib/prisma';
+import { getWebsite } from 'queries';
+import cache from 'lib/cache';
-export async function resetWebsite(websiteId) {
+export async function resetWebsite(id) {
const { client, transaction } = prisma;
+ const { revId } = await getWebsite({ id });
+
return transaction([
- client.pageview.deleteMany({
- where: { session: { website: { websiteUuid: websiteId } } },
- }),
- client.eventData.deleteMany({
- where: { event: { session: { website: { websiteUuid: websiteId } } } },
- }),
- client.event.deleteMany({
- where: { session: { website: { websiteUuid: websiteId } } },
+ client.websiteEvent.deleteMany({
+ where: { websiteId: id },
}),
client.session.deleteMany({
- where: { website: { websiteUuid: websiteId } },
+ where: { websiteId: id },
}),
- ]);
+ client.website.update({ where: { id }, data: { revId: revId + 1 } }),
+ ]).then(async data => {
+ if (cache.enabled) {
+ await cache.storeWebsite(data[2]);
+ }
+
+ return data;
+ });
}
diff --git a/queries/admin/website/updateWebsite.js b/queries/admin/website/updateWebsite.js
index 1a5079a9..5ac70a61 100644
--- a/queries/admin/website/updateWebsite.js
+++ b/queries/admin/website/updateWebsite.js
@@ -1,8 +1,10 @@
import prisma from 'lib/prisma';
-export async function updateWebsite(data, where) {
+export async function updateWebsite(id, data) {
return prisma.client.website.update({
- where,
+ where: {
+ id,
+ },
data,
});
}
diff --git a/queries/analytics/event/getEventData.js b/queries/analytics/event/getEventData.js
index 91302d30..f7d725c1 100644
--- a/queries/analytics/event/getEventData.js
+++ b/queries/analytics/event/getEventData.js
@@ -1,11 +1,16 @@
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import prisma from 'lib/prisma';
+import cache from 'lib/cache';
export async function getEventData(...args) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
+ }).then(results => {
+ return Object.keys(results[0]).map(a => {
+ return { x: a, y: results[0][`${a}`] };
+ });
});
}
@@ -21,7 +26,7 @@ async function relationalQuery(websiteId, { startDate, endDate, event_name, colu
on event.website_id = website.website_id
join event_data
on event.event_id = event_data.event_id
- where website_uuid='${websiteId}'
+ where website.website_id ='${websiteId}'
and event.created_at between $1 and $2
${event_name ? `and event_name = ${event_name}` : ''}
${
@@ -30,23 +35,21 @@ async function relationalQuery(websiteId, { startDate, endDate, event_name, colu
: ''
}`,
params,
- ).then(results => {
- return Object.keys(results[0]).map(a => {
- return { x: a, y: results[0][`${a}`] };
- });
- });
+ );
}
async function clickhouseQuery(websiteId, { startDate, endDate, event_name, columns, filters }) {
const { rawQuery, getBetweenDates, getEventDataColumnsQuery, getEventDataFilterQuery } =
clickhouse;
- const params = [websiteId];
+ const website = await cache.fetchWebsite(websiteId);
+ const params = [websiteId, website?.revId || 0];
return rawQuery(
`select
${getEventDataColumnsQuery('event_data', columns)}
from event
- where website_id= $1
+ where website_id = $1
+ and rev_id = $2
${event_name ? `and event_name = ${event_name}` : ''}
and ${getBetweenDates('created_at', startDate, endDate)}
${
@@ -55,9 +58,5 @@ async function clickhouseQuery(websiteId, { startDate, endDate, event_name, colu
: ''
}`,
params,
- ).then(results => {
- return Object.keys(results[0]).map(a => {
- return { x: a, y: results[0][`${a}`] };
- });
- });
+ );
}
diff --git a/queries/analytics/event/getEventMetrics.js b/queries/analytics/event/getEventMetrics.js
index 605bb688..27ee6d04 100644
--- a/queries/analytics/event/getEventMetrics.js
+++ b/queries/analytics/event/getEventMetrics.js
@@ -1,6 +1,7 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
+import cache from 'lib/cache';
export async function getEventMetrics(...args) {
return runQuery({
@@ -28,7 +29,7 @@ async function relationalQuery(
from event
join website
on event.website_id = website.website_id
- where website_uuid='${websiteId}'
+ where website.website_id='${websiteId}'
and event.created_at between $1 and $2
${getFilterQuery('event', filters, params)}
group by 1, 2
@@ -46,7 +47,8 @@ async function clickhouseQuery(
filters = {},
) {
const { rawQuery, getDateQuery, getBetweenDates, getFilterQuery } = clickhouse;
- const params = [websiteId];
+ const website = await cache.fetchWebsite(websiteId);
+ const params = [websiteId, website?.revId || 0];
return rawQuery(
`select
@@ -55,7 +57,8 @@ async function clickhouseQuery(
count(*) y
from event
where event_name != ''
- and website_id= $1
+ and website_id = $1
+ and rev_id = $2
and ${getBetweenDates('created_at', start_at, end_at)}
${getFilterQuery('event', filters, params)}
group by x, t
diff --git a/queries/analytics/event/getEvents.js b/queries/analytics/event/getEvents.js
index ef2ee4e4..81a187ce 100644
--- a/queries/analytics/event/getEvents.js
+++ b/queries/analytics/event/getEvents.js
@@ -12,10 +12,8 @@ export function getEvents(...args) {
function relationalQuery(websites, start_at) {
return prisma.client.event.findMany({
where: {
- website: {
- websiteUuid: {
- in: websites,
- },
+ websiteId: {
+ in: websites,
},
createdAt: {
gte: start_at,
diff --git a/queries/analytics/event/saveEvent.js b/queries/analytics/event/saveEvent.js
index de7a1cae..3bb3b0bf 100644
--- a/queries/analytics/event/saveEvent.js
+++ b/queries/analytics/event/saveEvent.js
@@ -2,6 +2,8 @@ import { EVENT_NAME_LENGTH, URL_LENGTH } from 'lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import kafka from 'lib/kafka';
import prisma from 'lib/prisma';
+import { uuid } from 'lib/crypto';
+import cache from 'lib/cache';
export async function saveEvent(...args) {
return runQuery({
@@ -10,48 +12,51 @@ export async function saveEvent(...args) {
});
}
-async function relationalQuery(
- { websiteId },
- { session: { id: sessionId }, eventUuid, url, eventName, eventData },
-) {
- const data = {
+async function relationalQuery(data) {
+ const { websiteId, sessionId, url, eventName, eventData } = data;
+ const eventId = uuid();
+
+ const params = {
+ id: eventId,
websiteId,
sessionId,
url: url?.substring(0, URL_LENGTH),
eventName: eventName?.substring(0, EVENT_NAME_LENGTH),
- eventUuid,
};
if (eventData) {
- data.eventData = {
+ params.eventData = {
create: {
+ id: eventId,
eventData: eventData,
},
};
}
return prisma.client.event.create({
- data,
+ data: params,
});
}
-async function clickhouseQuery(
- { websiteUuid: websiteId },
- { session: { country, sessionUuid, ...sessionArgs }, eventUuid, url, eventName, eventData },
-) {
+async function clickhouseQuery(data) {
+ const { websiteId, id: sessionId, url, eventName, eventData, country, ...args } = data;
const { getDateFormat, sendMessage } = kafka;
+ const website = await cache.fetchWebsite(websiteId);
const params = {
- session_id: sessionUuid,
- event_id: eventUuid,
website_id: websiteId,
- created_at: getDateFormat(new Date()),
+ session_id: sessionId,
+ event_id: uuid(),
url: url?.substring(0, URL_LENGTH),
event_name: eventName?.substring(0, EVENT_NAME_LENGTH),
event_data: eventData ? JSON.stringify(eventData) : null,
- ...sessionArgs,
+ rev_id: website?.revId || 0,
+ created_at: getDateFormat(new Date()),
country: country ? country : null,
+ ...args,
};
await sendMessage(params, 'event');
+
+ return data;
}
diff --git a/queries/analytics/pageview/getPageviewMetrics.js b/queries/analytics/pageview/getPageviewMetrics.js
index 69607d00..8dfd6595 100644
--- a/queries/analytics/pageview/getPageviewMetrics.js
+++ b/queries/analytics/pageview/getPageviewMetrics.js
@@ -1,6 +1,7 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
+import cache from 'lib/cache';
export async function getPageviewMetrics(...args) {
return runQuery({
@@ -24,7 +25,7 @@ async function relationalQuery(websiteId, { startDate, endDate, column, table, f
from ${table}
${` join website on ${table}.website_id = website.website_id`}
${joinSession}
- where website.website_uuid='${websiteId}'
+ where website.website_id='${websiteId}'
and ${table}.created_at between $1 and $2
${pageviewQuery}
${joinSession && sessionQuery}
@@ -37,13 +38,15 @@ async function relationalQuery(websiteId, { startDate, endDate, column, table, f
async function clickhouseQuery(websiteId, { startDate, endDate, column, filters = {} }) {
const { rawQuery, parseFilters, getBetweenDates } = clickhouse;
- const params = [websiteId];
+ const website = await cache.fetchWebsite(websiteId);
+ const params = [websiteId, website?.revId || 0];
const { pageviewQuery, sessionQuery, eventQuery } = parseFilters(column, filters, params);
return rawQuery(
`select ${column} x, count(*) y
from event
- where website_id= $1
+ where website_id = $1
+ and rev_id = $2
${column !== 'event_name' ? `and event_name = ''` : `and event_name != ''`}
and ${getBetweenDates('created_at', startDate, endDate)}
${pageviewQuery}
diff --git a/queries/analytics/pageview/getPageviewParams.js b/queries/analytics/pageview/getPageviewParams.js
index 5cdabfa3..ce96c25b 100644
--- a/queries/analytics/pageview/getPageviewParams.js
+++ b/queries/analytics/pageview/getPageviewParams.js
@@ -24,7 +24,7 @@ async function relationalQuery(websiteId, start_at, end_at, column, table, filte
from ${table}
${` join website on ${table}.website_id = website.website_id`}
${joinSession}
- where website.website_uuid='${websiteId}'
+ where website.website_id='${websiteId}'
and ${table}.created_at between $1 and $2
and ${table}.url like '%?%'
${pageviewQuery}
diff --git a/queries/analytics/pageview/getPageviewStats.js b/queries/analytics/pageview/getPageviewStats.js
index 5ec8339f..c711d448 100644
--- a/queries/analytics/pageview/getPageviewStats.js
+++ b/queries/analytics/pageview/getPageviewStats.js
@@ -1,6 +1,7 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
+import cache from 'lib/cache';
export async function getPageviewStats(...args) {
return runQuery({
@@ -37,7 +38,7 @@ async function relationalQuery(
join website
on pageview.website_id = website.website_id
${joinSession}
- where website.website_uuid='${websiteId}'
+ where website.website_id='${websiteId}'
and pageview.created_at between $1 and $2
${pageviewQuery}
${sessionQuery}
@@ -51,7 +52,8 @@ async function clickhouseQuery(
{ start_at, end_at, timezone = 'UTC', unit = 'day', count = '*', filters = {} },
) {
const { parseFilters, rawQuery, getDateStringQuery, getDateQuery, getBetweenDates } = clickhouse;
- const params = [websiteId];
+ const website = await cache.fetchWebsite(websiteId);
+ const params = [websiteId, website?.revId || 0];
const { pageviewQuery, sessionQuery } = parseFilters(null, filters, params);
return rawQuery(
@@ -64,7 +66,8 @@ async function clickhouseQuery(
count(${count !== '*' ? 'distinct session_id' : count}) y
from event
where event_name = ''
- and website_id= $1
+ and website_id = $1
+ and rev_id = $2
and ${getBetweenDates('created_at', start_at, end_at)}
${pageviewQuery}
${sessionQuery}
diff --git a/queries/analytics/pageview/getPageviews.js b/queries/analytics/pageview/getPageviews.js
index 923317c0..2bf41b0b 100644
--- a/queries/analytics/pageview/getPageviews.js
+++ b/queries/analytics/pageview/getPageviews.js
@@ -12,10 +12,8 @@ export async function getPageviews(...args) {
async function relationalQuery(websites, start_at) {
return prisma.client.pageview.findMany({
where: {
- website: {
- websiteUuid: {
- in: websites,
- },
+ websiteId: {
+ in: websites,
},
createdAt: {
gte: start_at,
@@ -35,11 +33,11 @@ async function clickhouseQuery(websites, start_at) {
url
from event
where event_name = ''
- and ${
- websites && websites.length > 0
- ? `website_id in (${getCommaSeparatedStringFormat(websites)})`
- : '0 = 0'
- }
+ and ${
+ websites && websites.length > 0
+ ? `website_id in (${getCommaSeparatedStringFormat(websites)})`
+ : '0 = 0'
+ }
and created_at >= ${clickhouse.getDateFormat(start_at)}`,
);
}
diff --git a/queries/analytics/pageview/savePageView.js b/queries/analytics/pageview/savePageView.js
index 65f04331..adcb4b3f 100644
--- a/queries/analytics/pageview/savePageView.js
+++ b/queries/analytics/pageview/savePageView.js
@@ -2,6 +2,8 @@ import { URL_LENGTH } from 'lib/constants';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import kafka from 'lib/kafka';
import prisma from 'lib/prisma';
+import cache from 'lib/cache';
+import { uuid } from 'lib/crypto';
export async function savePageView(...args) {
return runQuery({
@@ -10,9 +12,11 @@ export async function savePageView(...args) {
});
}
-async function relationalQuery({ websiteId }, { session: { id: sessionId }, url, referrer }) {
+async function relationalQuery(data) {
+ const { websiteId, sessionId, url, referrer } = data;
return prisma.client.pageview.create({
data: {
+ id: uuid(),
websiteId,
sessionId,
url: url?.substring(0, URL_LENGTH),
@@ -21,20 +25,23 @@ async function relationalQuery({ websiteId }, { session: { id: sessionId }, url,
});
}
-async function clickhouseQuery(
- { websiteUuid: websiteId },
- { session: { country, sessionUuid, ...sessionArgs }, url, referrer },
-) {
+async function clickhouseQuery(data) {
+ const { websiteId, id: sessionId, url, referrer, country, ...args } = data;
+ const website = await cache.fetchWebsite(websiteId);
const { getDateFormat, sendMessage } = kafka;
- const params = {
- session_uuid: sessionUuid,
+
+ const msg = {
+ session_id: sessionId,
website_id: websiteId,
- created_at: getDateFormat(new Date()),
url: url?.substring(0, URL_LENGTH),
referrer: referrer?.substring(0, URL_LENGTH),
- ...sessionArgs,
+ rev_id: website?.revId || 0,
+ created_at: getDateFormat(new Date()),
country: country ? country : null,
+ ...args,
};
- await sendMessage(params, 'event');
+ await sendMessage(msg, 'event');
+
+ return data;
}
diff --git a/queries/analytics/session/createSession.js b/queries/analytics/session/createSession.js
index 824ed252..f401a20f 100644
--- a/queries/analytics/session/createSession.js
+++ b/queries/analytics/session/createSession.js
@@ -1,65 +1,45 @@
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import kafka from 'lib/kafka';
import prisma from 'lib/prisma';
-import redis from 'lib/redis';
+import cache from 'lib/cache';
export async function createSession(...args) {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args),
+ }).then(async data => {
+ if (cache.enabled) {
+ await cache.storeSession(data);
+ }
+
+ return data;
});
}
-async function relationalQuery(websiteId, data) {
- return prisma.client.session
- .create({
- data: {
- websiteId,
- ...data,
- },
- select: {
- id: true,
- sessionUuid: true,
- hostname: true,
- browser: true,
- os: true,
- screen: true,
- language: true,
- country: true,
- device: true,
- },
- })
- .then(async res => {
- if (redis.enabled && res) {
- await redis.set(`session:${res.sessionUuid}`, 1);
- }
-
- return res;
- });
+async function relationalQuery(data) {
+ return prisma.client.session.create({ data });
}
-async function clickhouseQuery(
- websiteId,
- { sessionUuid, hostname, browser, os, screen, language, country, device },
-) {
+async function clickhouseQuery(data) {
+ const { id, websiteId, hostname, browser, os, device, screen, language, country } = data;
const { getDateFormat, sendMessage } = kafka;
+ const website = await cache.fetchWebsite(websiteId);
- const params = {
- session_uuid: sessionUuid,
+ const msg = {
+ session_id: id,
website_id: websiteId,
- created_at: getDateFormat(new Date()),
hostname,
browser,
os,
device,
screen,
language,
- country: country ? country : null,
+ country,
+ rev_id: website?.revId || 0,
+ created_at: getDateFormat(new Date()),
};
- await sendMessage(params, 'event');
+ await sendMessage(msg, 'event');
- if (redis.enabled) {
- await redis.set(`session:${sessionUuid}`, 1);
- }
+ return data;
}
diff --git a/queries/analytics/session/getSession.js b/queries/analytics/session/getSession.js
new file mode 100644
index 00000000..adc9acd8
--- /dev/null
+++ b/queries/analytics/session/getSession.js
@@ -0,0 +1,39 @@
+import clickhouse from 'lib/clickhouse';
+import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
+import prisma from 'lib/prisma';
+
+export async function getSession(...args) {
+ return runQuery({
+ [PRISMA]: () => relationalQuery(...args),
+ [CLICKHOUSE]: () => clickhouseQuery(...args),
+ });
+}
+
+async function relationalQuery(where) {
+ return prisma.client.session.findUnique({
+ where,
+ });
+}
+
+async function clickhouseQuery({ id: sessionId }) {
+ const { rawQuery, findFirst } = clickhouse;
+ const params = [sessionId];
+
+ return rawQuery(
+ `select
+ session_id,
+ website_id,
+ created_at,
+ hostname,
+ browser,
+ os,
+ device,
+ screen,
+ language,
+ country
+ from event
+ where session_id = $1
+ limit 1`,
+ params,
+ ).then(result => findFirst(result));
+}
diff --git a/queries/analytics/session/getSessionByUuid.js b/queries/analytics/session/getSessionByUuid.js
deleted file mode 100644
index 2bb29c01..00000000
--- a/queries/analytics/session/getSessionByUuid.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import clickhouse from 'lib/clickhouse';
-import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
-import prisma from 'lib/prisma';
-import redis from 'lib/redis';
-
-export async function getSessionByUuid(...args) {
- return runQuery({
- [PRISMA]: () => relationalQuery(...args),
- [CLICKHOUSE]: () => clickhouseQuery(...args),
- });
-}
-
-async function relationalQuery(sessionUuid) {
- return prisma.client.session
- .findUnique({
- where: {
- sessionUuid,
- },
- })
- .then(async res => {
- if (redis.enabled && res) {
- await redis.set(`session:${res.sessionUuid}`, 1);
- }
-
- return res;
- });
-}
-
-async function clickhouseQuery(sessionUuid) {
- const { rawQuery, findFirst } = clickhouse;
- const params = [sessionUuid];
-
- return rawQuery(
- `select distinct
- session_id,
- website_id,
- created_at,
- hostname,
- browser,
- os,
- device,
- screen,
- language,
- country
- from event
- where session_id = $1`,
- params,
- )
- .then(result => findFirst(result))
- .then(async res => {
- if (redis.enabled && res) {
- await redis.set(`session:${res.session_uuid}`, 1);
- }
-
- return res;
- });
-}
diff --git a/queries/analytics/session/getSessionMetrics.js b/queries/analytics/session/getSessionMetrics.js
index 020bddfb..6e74e9b6 100644
--- a/queries/analytics/session/getSessionMetrics.js
+++ b/queries/analytics/session/getSessionMetrics.js
@@ -1,6 +1,7 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
+import cache from 'lib/cache';
export async function getSessionMetrics(...args) {
return runQuery({
@@ -23,7 +24,7 @@ async function relationalQuery(websiteId, { startDate, endDate, field, filters =
join website
on pageview.website_id = website.website_id
${joinSession}
- where website.website_uuid='${websiteId}'
+ where website.website_id='${websiteId}'
and pageview.created_at between $1 and $2
${pageviewQuery}
${sessionQuery}
@@ -36,13 +37,15 @@ async function relationalQuery(websiteId, { startDate, endDate, field, filters =
async function clickhouseQuery(websiteId, { startDate, endDate, field, filters = {} }) {
const { parseFilters, getBetweenDates, rawQuery } = clickhouse;
- const params = [websiteId];
+ const website = await cache.fetchWebsite(websiteId);
+ const params = [websiteId, website?.revId || 0];
const { pageviewQuery, sessionQuery } = parseFilters(null, filters, params);
return rawQuery(
`select ${field} x, count(*) y
from event as x
- where website_id=$1
+ where website_id = $1
+ and rev_id = $2
and event_name = ''
and ${getBetweenDates('created_at', startDate, endDate)}
${pageviewQuery}
diff --git a/queries/analytics/session/getSessions.js b/queries/analytics/session/getSessions.js
index 85e33248..c5fed485 100644
--- a/queries/analytics/session/getSessions.js
+++ b/queries/analytics/session/getSessions.js
@@ -14,10 +14,8 @@ async function relationalQuery(websites, start_at) {
where: {
...(websites && websites.length > 0
? {
- website: {
- websiteUuid: {
- in: websites,
- },
+ websiteId: {
+ in: websites,
},
}
: {}),
diff --git a/queries/analytics/stats/getActiveVisitors.js b/queries/analytics/stats/getActiveVisitors.js
index 3a898d94..c7592e5b 100644
--- a/queries/analytics/stats/getActiveVisitors.js
+++ b/queries/analytics/stats/getActiveVisitors.js
@@ -19,7 +19,7 @@ async function relationalQuery(websiteId) {
from pageview
join website
on pageview.website_id = website.website_id
- where website.website_uuid = '${websiteId}'
+ where website.website_id = '${websiteId}'
and pageview.created_at >= $1`,
params,
);
diff --git a/queries/analytics/stats/getWebsiteStats.js b/queries/analytics/stats/getWebsiteStats.js
index 134e1c3e..002d8a9c 100644
--- a/queries/analytics/stats/getWebsiteStats.js
+++ b/queries/analytics/stats/getWebsiteStats.js
@@ -1,6 +1,7 @@
import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db';
+import cache from 'lib/cache';
export async function getWebsiteStats(...args) {
return runQuery({
@@ -33,7 +34,7 @@ async function relationalQuery(websiteId, { start_at, end_at, filters = {} }) {
join website
on pageview.website_id = website.website_id
${joinSession}
- where website.website_uuid='${websiteId}'
+ where website.website_id='${websiteId}'
and pageview.created_at between $1 and $2
${pageviewQuery}
${sessionQuery}
@@ -45,7 +46,8 @@ async function relationalQuery(websiteId, { start_at, end_at, filters = {} }) {
async function clickhouseQuery(websiteId, { start_at, end_at, filters = {} }) {
const { rawQuery, getDateQuery, getBetweenDates, parseFilters } = clickhouse;
- const params = [websiteId];
+ const website = await cache.fetchWebsite(websiteId);
+ const params = [websiteId, website?.revId || 0];
const { pageviewQuery, sessionQuery } = parseFilters(null, filters, params);
return rawQuery(
@@ -63,6 +65,7 @@ async function clickhouseQuery(websiteId, { start_at, end_at, filters = {} }) {
from event
where event_name = ''
and website_id = $1
+ and rev_id = $2
and ${getBetweenDates('created_at', start_at, end_at)}
${pageviewQuery}
${sessionQuery}
diff --git a/queries/index.js b/queries/index.js
index a570af65..4cdcedd9 100644
--- a/queries/index.js
+++ b/queries/index.js
@@ -1,8 +1,8 @@
-export * from './admin/account/createAccount';
-export * from './admin/account/deleteAccount';
-export * from './admin/account/getAccount';
-export * from './admin/account/getAccounts';
-export * from './admin/account/updateAccount';
+export * from './admin/user/createUser';
+export * from './admin/user/deleteUser';
+export * from './admin/user/getUser';
+export * from './admin/user/getUsers';
+export * from './admin/user/updateUser';
export * from './admin/website/createWebsite';
export * from './admin/website/deleteWebsite';
export * from './admin/website/getAllWebsites';
@@ -20,7 +20,7 @@ export * from './analytics/pageview/getPageviews';
export * from './analytics/pageview/getPageviewStats';
export * from './analytics/pageview/savePageView';
export * from './analytics/session/createSession';
-export * from './analytics/session/getSessionByUuid';
+export * from './analytics/session/getSession';
export * from './analytics/session/getSessionMetrics';
export * from './analytics/session/getSessions';
export * from './analytics/stats/getActiveVisitors';
diff --git a/scripts/build-geo.js b/scripts/build-geo.js
index a4554739..2ee601db 100644
--- a/scripts/build-geo.js
+++ b/scripts/build-geo.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
require('dotenv').config();
const fs = require('fs');
const path = require('path');
diff --git a/scripts/change-password.js b/scripts/change-password.js
index a9b63c12..b12373a9 100644
--- a/scripts/change-password.js
+++ b/scripts/change-password.js
@@ -13,9 +13,9 @@ const runQuery = async query => {
});
};
-const updateAccountByUsername = (username, data) => {
+const updateUserByUsername = (username, data) => {
return runQuery(
- prisma.account.update({
+ prisma.user.update({
where: {
username,
},
@@ -26,7 +26,7 @@ const updateAccountByUsername = (username, data) => {
const changePassword = async (username, newPassword) => {
const password = hashPassword(newPassword);
- return updateAccountByUsername(username, { password });
+ return updateUserByUsername(username, { password });
};
const getUsernameAndPassword = async () => {
@@ -40,7 +40,7 @@ const getUsernameAndPassword = async () => {
questions.push({
type: 'text',
name: 'username',
- message: 'Enter account to change password',
+ message: 'Enter user to change password',
});
}
if (!password) {
@@ -84,7 +84,7 @@ const getUsernameAndPassword = async () => {
console.log('Password changed for user', chalk.greenBright(username));
} catch (error) {
if (error.meta.cause.includes('Record to update not found')) {
- console.log('Account not found:', chalk.redBright(username));
+ console.log('User not found:', chalk.redBright(username));
} else {
throw error;
}
diff --git a/scripts/check-db.js b/scripts/check-db.js
index d5cc2a64..9b5a47a8 100644
--- a/scripts/check-db.js
+++ b/scripts/check-db.js
@@ -40,7 +40,7 @@ async function checkConnection() {
async function checkTables() {
try {
- await prisma.$queryRaw`select * from account limit 1`;
+ await prisma.$queryRaw`select * from user limit 1`;
success('Database tables found.');
} catch (e) {
diff --git a/scripts/check-lang.js b/scripts/check-lang.js
index a916180a..e5a0bf09 100644
--- a/scripts/check-lang.js
+++ b/scripts/check-lang.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
diff --git a/scripts/copy-db-files.js b/scripts/copy-db-files.js
index 3e902d45..15c34674 100644
--- a/scripts/copy-db-files.js
+++ b/scripts/copy-db-files.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
require('dotenv').config();
const fse = require('fs-extra');
const path = require('path');
diff --git a/scripts/download-country-names.js b/scripts/download-country-names.js
index a4116498..a180e7af 100644
--- a/scripts/download-country-names.js
+++ b/scripts/download-country-names.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
const fs = require('fs-extra');
const path = require('path');
const https = require('https');
diff --git a/scripts/download-language-names.js b/scripts/download-language-names.js
index a804763d..5cea88cf 100644
--- a/scripts/download-language-names.js
+++ b/scripts/download-language-names.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
const fs = require('fs-extra');
const path = require('path');
const https = require('https');
diff --git a/scripts/merge-lang.js b/scripts/merge-lang.js
index f1b86df9..4cdd201c 100644
--- a/scripts/merge-lang.js
+++ b/scripts/merge-lang.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const prettier = require('prettier');
diff --git a/scripts/update-tracker.js b/scripts/update-tracker.js
index 6b9e96c7..fc8ad8b8 100644
--- a/scripts/update-tracker.js
+++ b/scripts/update-tracker.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
require('dotenv').config();
const fs = require('fs');
const path = require('path');
diff --git a/sql/schema.mysql.sql b/sql/schema.mysql.sql
deleted file mode 100644
index 0ae7775a..00000000
--- a/sql/schema.mysql.sql
+++ /dev/null
@@ -1,102 +0,0 @@
--- CreateTable
-CREATE TABLE `account` (
- `user_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- `username` VARCHAR(255) NOT NULL,
- `password` VARCHAR(60) NOT NULL,
- `is_admin` BOOLEAN NOT NULL DEFAULT false,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
- `updated_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
-
- UNIQUE INDEX `username`(`username`),
- PRIMARY KEY (`user_id`)
-) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `event` (
- `event_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- `website_id` INTEGER UNSIGNED NOT NULL,
- `session_id` INTEGER UNSIGNED NOT NULL,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
- `url` VARCHAR(500) NOT NULL,
- `event_type` VARCHAR(50) NOT NULL,
- `event_value` VARCHAR(50) NOT NULL,
-
- INDEX `event_created_at_idx`(`created_at`),
- INDEX `event_session_id_idx`(`session_id`),
- INDEX `event_website_id_idx`(`website_id`),
- PRIMARY KEY (`event_id`)
-) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `pageview` (
- `view_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- `website_id` INTEGER UNSIGNED NOT NULL,
- `session_id` INTEGER UNSIGNED NOT NULL,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
- `url` VARCHAR(500) NOT NULL,
- `referrer` VARCHAR(500) NULL,
-
- INDEX `pageview_created_at_idx`(`created_at`),
- INDEX `pageview_session_id_idx`(`session_id`),
- INDEX `pageview_website_id_created_at_idx`(`website_id`, `created_at`),
- INDEX `pageview_website_id_idx`(`website_id`),
- INDEX `pageview_website_id_session_id_created_at_idx`(`website_id`, `session_id`, `created_at`),
- PRIMARY KEY (`view_id`)
-) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `session` (
- `session_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- `session_uuid` VARCHAR(36) NOT NULL,
- `website_id` INTEGER UNSIGNED NOT NULL,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
- `hostname` VARCHAR(100) NULL,
- `browser` VARCHAR(20) NULL,
- `os` VARCHAR(20) NULL,
- `device` VARCHAR(20) NULL,
- `screen` VARCHAR(11) NULL,
- `language` VARCHAR(35) NULL,
- `country` CHAR(2) NULL,
-
- UNIQUE INDEX `session_uuid`(`session_uuid`),
- INDEX `session_created_at_idx`(`created_at`),
- INDEX `session_website_id_idx`(`website_id`),
- PRIMARY KEY (`session_id`)
-) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `website` (
- `website_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
- `website_uuid` VARCHAR(36) NOT NULL,
- `user_id` INTEGER UNSIGNED NOT NULL,
- `name` VARCHAR(100) NOT NULL,
- `domain` VARCHAR(500) NULL,
- `share_id` VARCHAR(64) NULL,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
-
- UNIQUE INDEX `website_uuid`(`website_uuid`),
- UNIQUE INDEX `share_id`(`share_id`),
- INDEX `website_user_id_idx`(`user_id`),
- PRIMARY KEY (`website_id`)
-) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- AddForeignKey
-ALTER TABLE `event` ADD CONSTRAINT `event_ibfk_2` FOREIGN KEY (`session_id`) REFERENCES `session`(`session_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
--- AddForeignKey
-ALTER TABLE `event` ADD CONSTRAINT `event_ibfk_1` FOREIGN KEY (`website_id`) REFERENCES `website`(`website_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
--- AddForeignKey
-ALTER TABLE `pageview` ADD CONSTRAINT `pageview_ibfk_2` FOREIGN KEY (`session_id`) REFERENCES `session`(`session_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
--- AddForeignKey
-ALTER TABLE `pageview` ADD CONSTRAINT `pageview_ibfk_1` FOREIGN KEY (`website_id`) REFERENCES `website`(`website_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
--- AddForeignKey
-ALTER TABLE `session` ADD CONSTRAINT `session_ibfk_1` FOREIGN KEY (`website_id`) REFERENCES `website`(`website_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
--- AddForeignKey
-ALTER TABLE `website` ADD CONSTRAINT `website_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `account`(`user_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-
--- CreateAdminUser
-INSERT INTO account (username, password, is_admin) values ('admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa', true);
diff --git a/sql/schema.postgresql.sql b/sql/schema.postgresql.sql
deleted file mode 100644
index cdabcd17..00000000
--- a/sql/schema.postgresql.sql
+++ /dev/null
@@ -1,132 +0,0 @@
--- CreateTable
-CREATE TABLE "account" (
- "user_id" SERIAL NOT NULL,
- "username" VARCHAR(255) NOT NULL,
- "password" VARCHAR(60) NOT NULL,
- "is_admin" BOOLEAN NOT NULL DEFAULT false,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "updated_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
-
- PRIMARY KEY ("user_id")
-);
-
--- CreateTable
-CREATE TABLE "event" (
- "event_id" SERIAL NOT NULL,
- "website_id" INTEGER NOT NULL,
- "session_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "url" VARCHAR(500) NOT NULL,
- "event_type" VARCHAR(50) NOT NULL,
- "event_value" VARCHAR(50) NOT NULL,
-
- PRIMARY KEY ("event_id")
-);
-
--- CreateTable
-CREATE TABLE "pageview" (
- "view_id" SERIAL NOT NULL,
- "website_id" INTEGER NOT NULL,
- "session_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "url" VARCHAR(500) NOT NULL,
- "referrer" VARCHAR(500),
-
- PRIMARY KEY ("view_id")
-);
-
--- CreateTable
-CREATE TABLE "session" (
- "session_id" SERIAL NOT NULL,
- "session_uuid" UUID NOT NULL,
- "website_id" INTEGER NOT NULL,
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
- "hostname" VARCHAR(100),
- "browser" VARCHAR(20),
- "os" VARCHAR(20),
- "device" VARCHAR(20),
- "screen" VARCHAR(11),
- "language" VARCHAR(35),
- "country" CHAR(2),
-
- PRIMARY KEY ("session_id")
-);
-
--- CreateTable
-CREATE TABLE "website" (
- "website_id" SERIAL NOT NULL,
- "website_uuid" UUID NOT NULL,
- "user_id" INTEGER NOT NULL,
- "name" VARCHAR(100) NOT NULL,
- "domain" VARCHAR(500),
- "share_id" VARCHAR(64),
- "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
-
- PRIMARY KEY ("website_id")
-);
-
--- CreateIndex
-CREATE UNIQUE INDEX "account.username_unique" ON "account"("username");
-
--- CreateIndex
-CREATE INDEX "event_created_at_idx" ON "event"("created_at");
-
--- CreateIndex
-CREATE INDEX "event_session_id_idx" ON "event"("session_id");
-
--- CreateIndex
-CREATE INDEX "event_website_id_idx" ON "event"("website_id");
-
--- CreateIndex
-CREATE INDEX "pageview_created_at_idx" ON "pageview"("created_at");
-
--- CreateIndex
-CREATE INDEX "pageview_session_id_idx" ON "pageview"("session_id");
-
--- CreateIndex
-CREATE INDEX "pageview_website_id_created_at_idx" ON "pageview"("website_id", "created_at");
-
--- CreateIndex
-CREATE INDEX "pageview_website_id_idx" ON "pageview"("website_id");
-
--- CreateIndex
-CREATE INDEX "pageview_website_id_session_id_created_at_idx" ON "pageview"("website_id", "session_id", "created_at");
-
--- CreateIndex
-CREATE UNIQUE INDEX "session.session_uuid_unique" ON "session"("session_uuid");
-
--- CreateIndex
-CREATE INDEX "session_created_at_idx" ON "session"("created_at");
-
--- CreateIndex
-CREATE INDEX "session_website_id_idx" ON "session"("website_id");
-
--- CreateIndex
-CREATE UNIQUE INDEX "website.website_uuid_unique" ON "website"("website_uuid");
-
--- CreateIndex
-CREATE UNIQUE INDEX "website.share_id_unique" ON "website"("share_id");
-
--- CreateIndex
-CREATE INDEX "website_user_id_idx" ON "website"("user_id");
-
--- AddForeignKey
-ALTER TABLE "event" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "event" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "pageview" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "pageview" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "session" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- AddForeignKey
-ALTER TABLE "website" ADD FOREIGN KEY ("user_id") REFERENCES "account"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
-
--- CreateAdminUser
-INSERT INTO account (username, password, is_admin) values ('admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa', true);
diff --git a/tracker/index.js b/tracker/index.js
index 3edca438..a9e11d0d 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -92,21 +92,21 @@
.then(text => (cache = text));
};
- const trackView = (url = currentUrl, referrer = currentRef, websiteUuid = website) =>
+ const trackView = (url = currentUrl, referrer = currentRef, websiteId = website) =>
collect(
'pageview',
assign(getPayload(), {
- website: websiteUuid,
+ website: websiteId,
url,
referrer,
}),
);
- const trackEvent = (eventName, eventData, url = currentUrl, websiteUuid = website) =>
+ const trackEvent = (eventName, eventData, url = currentUrl, websiteId = website) =>
collect(
'event',
assign(getPayload(), {
- website: websiteUuid,
+ website: websiteId,
url,
event_name: eventName,
event_data: eventData,
diff --git a/yarn.lock b/yarn.lock
index 7df9a095..f0229aa3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5344,10 +5344,10 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
-next-basics@^0.18.0:
- version "0.18.0"
- resolved "https://registry.yarnpkg.com/next-basics/-/next-basics-0.18.0.tgz#7683ec67d562fb57a6f3fb346c989802157450d1"
- integrity sha512-KJZjAHahQ++YYaSPuJVAY0wH9QYXVq85ZUGW5mBMq+qDfL8ZVG4nTWc8GhuI8EYLLR+oZp/eInvoqYgiW/dbhg==
+next-basics@^0.23.0:
+ version "0.23.0"
+ resolved "https://registry.yarnpkg.com/next-basics/-/next-basics-0.23.0.tgz#6ccc4b5f3cd87550b07d280c2a3bdee26f557912"
+ integrity sha512-AwKipMFkxS84ZKFDVSo8ZHXoOVzF2sPZp21pgtGlqf9aUmj48+QtxdM6ZnJVRg8Savf48ENjeYzeiu5I8Z4l/w==
dependencies:
base-x "^4.0.0"
bcryptjs "^2.4.3"