From 994cf950c806e7ebff5f53176eedb722c0b79159 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 25 Oct 2022 09:24:20 -0700 Subject: [PATCH 1/9] Fixed issue with realtime page rendering. --- components/metrics/RealtimeHeader.js | 9 ++++--- components/pages/RealtimeDashboard.js | 28 ++++++++++------------ package.json | 2 +- queries/analytics/stats/getRealtimeData.js | 18 +++++++------- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/components/metrics/RealtimeHeader.js b/components/metrics/RealtimeHeader.js index 40881538..1b93cd2f 100644 --- a/components/metrics/RealtimeHeader.js +++ b/components/metrics/RealtimeHeader.js @@ -9,11 +9,14 @@ import styles from './RealtimeHeader.module.css'; export default function RealtimeHeader({ websites, data, websiteId, onSelect }) { const options = [ - { label: , value: 0 }, + { + label: , + value: null, + }, ].concat( - websites.map(({ name, id }, index) => ({ + websites.map(({ name, websiteUuid }, index) => ({ label: name, - value: id, + value: websiteUuid, divider: index === 0, })), ); diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 506d3722..628df62b 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -32,7 +32,7 @@ export default function RealtimeDashboard() { const { locale } = useLocale(); const countryNames = useCountryNames(locale); const [data, setData] = useState(); - const [websiteId, setWebsiteId] = useState(0); + const [websiteUuid, setWebsiteUuid] = useState(null); const { data: init, loading } = useFetch('/realtime/init'); const { data: updates } = useFetch('/realtime/update', { params: { start_at: data?.timestamp }, @@ -50,17 +50,18 @@ export default function RealtimeDashboard() { if (data) { const { pageviews, sessions, events } = data; - if (websiteId) { + if (websiteUuid) { + const { id } = init.websites.find(n => n.websiteUuid === websiteUuid); return { - pageviews: filterWebsite(pageviews, websiteId), - sessions: filterWebsite(sessions, websiteId), - events: filterWebsite(events, websiteId), + pageviews: filterWebsite(pageviews, id), + sessions: filterWebsite(sessions, id), + events: filterWebsite(events, id), }; } } return data; - }, [data, websiteId]); + }, [data, websiteUuid]); const countries = useMemo(() => { if (realtimeData?.sessions) { @@ -117,25 +118,20 @@ export default function RealtimeDashboard() {
- +
- + - + diff --git a/package.json b/package.json index facf913a..e586774f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "1.39.1", + "version": "1.39.2", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", diff --git a/queries/analytics/stats/getRealtimeData.js b/queries/analytics/stats/getRealtimeData.js index 9e7eb673..659f6145 100644 --- a/queries/analytics/stats/getRealtimeData.js +++ b/queries/analytics/stats/getRealtimeData.js @@ -10,19 +10,19 @@ export async function getRealtimeData(websites, time) { ]); return { - pageviews: pageviews.map(({ pageviewId, ...props }) => ({ - __id: `p${pageviewId}`, - pageviewId, + pageviews: pageviews.map(({ id, ...props }) => ({ + __id: `p${id}`, + pageviewId: id, ...props, })), - sessions: sessions.map(({ sessionId, ...props }) => ({ - __id: `s${sessionId}`, - sessionId, + sessions: sessions.map(({ id, ...props }) => ({ + __id: `s${id}`, + sessionId: id, ...props, })), - events: events.map(({ eventId, ...props }) => ({ - __id: `e${eventId}`, - eventId, + events: events.map(({ id, ...props }) => ({ + __id: `e${id}`, + eventId: id, ...props, })), timestamp: Date.now(), From ebf439c5987a8e7480df6af81bc33907832c0b6b Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Tue, 25 Oct 2022 10:27:43 -0700 Subject: [PATCH 2/9] fix auth, add pg extension (#1596) --- components/forms/EventDataForm.js | 2 +- db/postgresql/migrations/04_add_uuid/migration.sql | 2 ++ lib/auth.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/forms/EventDataForm.js b/components/forms/EventDataForm.js index db8e7fbb..e236aa3d 100644 --- a/components/forms/EventDataForm.js +++ b/components/forms/EventDataForm.js @@ -22,7 +22,7 @@ export const filterOptions = [ { label: 'Count', value: 'count' }, { label: 'Average', value: 'avg' }, { label: 'Minimum', value: 'min' }, - { label: 'Maxmimum', value: 'max' }, + { label: 'Maximum', value: 'max' }, { label: 'Sum', value: 'sum' }, ]; diff --git a/db/postgresql/migrations/04_add_uuid/migration.sql b/db/postgresql/migrations/04_add_uuid/migration.sql index 21d4cf07..749cef6b 100644 --- a/db/postgresql/migrations/04_add_uuid/migration.sql +++ b/db/postgresql/migrations/04_add_uuid/migration.sql @@ -1,3 +1,5 @@ +-- CreateExtension +CREATE EXTENSION IF NOT EXISTS pgcrypto; -- AlterTable ALTER TABLE "account" ADD COLUMN "account_uuid" UUID NULL; diff --git a/lib/auth.js b/lib/auth.js index 446d5169..a1620845 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -49,7 +49,7 @@ export async function allowQuery(req) { } if (userId) { - const website = await getWebsite({ id }); + const website = await getWebsite({ websiteUuid: id }); return website && website.userId === userId; } From ac070d3ce94d1bd97ab5264a1b9cdc95dbebfc08 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 25 Oct 2022 10:45:56 -0700 Subject: [PATCH 3/9] Fixed change password issue. API refactoring. Closes #1592. --- components/forms/ChangePasswordForm.js | 6 +++--- lib/auth.js | 16 +++++++++++----- lib/constants.js | 3 +++ lib/session.js | 4 ++-- pages/api/accounts/[id]/password.js | 14 +++++++------- pages/api/accounts/index.js | 4 ++-- pages/api/auth/login.js | 4 ++-- pages/api/share/[id].js | 4 ++-- pages/api/websites/[id]/active.js | 3 ++- pages/api/websites/[id]/eventdata.js | 3 ++- pages/api/websites/[id]/events.js | 3 ++- pages/api/websites/[id]/index.js | 15 ++++++++------- pages/api/websites/[id]/metrics.js | 8 ++++---- pages/api/websites/[id]/pageviews.js | 3 ++- pages/api/websites/[id]/reset.js | 3 ++- pages/api/websites/[id]/stats.js | 3 ++- queries/admin/account/getAccountById.js | 9 --------- queries/admin/account/getAccountByUsername.js | 9 --------- queries/admin/website/deleteWebsite.js | 15 ++++++--------- queries/admin/website/getWebsite.js | 15 ++++++++++++--- queries/admin/website/getWebsiteById.js | 9 --------- queries/admin/website/getWebsiteByShareId.js | 9 --------- queries/admin/website/getWebsiteByUuid.js | 18 ------------------ queries/index.js | 5 ----- 24 files changed, 74 insertions(+), 111 deletions(-) delete mode 100644 queries/admin/account/getAccountById.js delete mode 100644 queries/admin/account/getAccountByUsername.js delete mode 100644 queries/admin/website/getWebsiteById.js delete mode 100644 queries/admin/website/getWebsiteByShareId.js delete mode 100644 queries/admin/website/getWebsiteByUuid.js diff --git a/components/forms/ChangePasswordForm.js b/components/forms/ChangePasswordForm.js index 4ee657e6..dcad6f17 100644 --- a/components/forms/ChangePasswordForm.js +++ b/components/forms/ChangePasswordForm.js @@ -9,7 +9,7 @@ import FormLayout, { FormRow, } from 'components/layout/FormLayout'; import useApi from 'hooks/useApi'; -import useUser from '../../hooks/useUser'; +import useUser from 'hooks/useUser'; const initialValues = { current_password: '', @@ -43,13 +43,13 @@ export default function ChangePasswordForm({ values, onSave, onClose }) { const { user } = useUser(); const handleSubmit = async values => { - const { ok, data } = await post(`/accounts/${user.userId}/password`, values); + const { ok, error } = await post(`/accounts/${user.accountUuid}/password`, values); if (ok) { onSave(); } else { setMessage( - data || , + error || , ); } }; diff --git a/lib/auth.js b/lib/auth.js index 446d5169..f43f2002 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,6 +1,6 @@ import { parseSecureToken, parseToken } from 'next-basics'; -import { getWebsite } from 'queries'; -import { SHARE_TOKEN_HEADER } from 'lib/constants'; +import { getAccount, getWebsite } from 'queries'; +import { SHARE_TOKEN_HEADER, TYPE_ACCOUNT, TYPE_WEBSITE } from 'lib/constants'; import { secret } from 'lib/crypto'; export function getAuthToken(req) { @@ -35,7 +35,7 @@ export function isValidToken(token, validation) { return false; } -export async function allowQuery(req) { +export async function allowQuery(req, type) { const { id } = req.query; const { userId, isAdmin, shareToken } = req.auth ?? {}; @@ -49,9 +49,15 @@ export async function allowQuery(req) { } if (userId) { - const website = await getWebsite({ id }); + if (type === TYPE_WEBSITE) { + const website = await getWebsite({ websiteUuid: id }); - return website && website.userId === userId; + return website && website.userId === userId; + } else if (type === TYPE_ACCOUNT) { + const account = await getAccount({ accountUuid: id }); + + return account && account.id === id; + } } return false; diff --git a/lib/constants.js b/lib/constants.js index feef540d..8b3ee8d0 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -21,6 +21,9 @@ export const DEFAULT_WEBSITE_LIMIT = 10; export const REALTIME_RANGE = 30; export const REALTIME_INTERVAL = 3000; +export const TYPE_WEBSITE = 'website'; +export const TYPE_ACCOUNT = 'account'; + export const THEME_COLORS = { light: { primary: '#2680eb', diff --git a/lib/session.js b/lib/session.js index acd5b6b0..d1a88768 100644 --- a/lib/session.js +++ b/lib/session.js @@ -4,7 +4,7 @@ import { secret, uuid } from 'lib/crypto'; import redis, { DELETED } from 'lib/redis'; import clickhouse from 'lib/clickhouse'; import { getClientInfo, getJsonBody } from 'lib/request'; -import { createSession, getSessionByUuid, getWebsiteByUuid } from 'queries'; +import { createSession, getSessionByUuid, getWebsite } from 'queries'; export async function getSession(req) { const { payload } = getJsonBody(req); @@ -38,7 +38,7 @@ export async function getSession(req) { // Check database if does not exists in Redis if (!websiteId) { - const website = await getWebsiteByUuid(websiteUuid); + const website = await getWebsite({ websiteUuid }); websiteId = website ? website.id : null; } diff --git a/pages/api/accounts/[id]/password.js b/pages/api/accounts/[id]/password.js index 3a46c56a..89649c20 100644 --- a/pages/api/accounts/[id]/password.js +++ b/pages/api/accounts/[id]/password.js @@ -1,4 +1,4 @@ -import { getAccountById, updateAccount } from 'queries'; +import { getAccount, updateAccount } from 'queries'; import { useAuth } from 'lib/middleware'; import { badRequest, @@ -8,21 +8,21 @@ import { checkPassword, hashPassword, } from 'next-basics'; +import { allowQuery } from 'lib/auth'; +import { TYPE_ACCOUNT } from 'lib/constants'; export default async (req, res) => { await useAuth(req, res); - const { userId: currentUserId, isAdmin: currentUserIsAdmin } = req.auth; const { current_password, new_password } = req.body; - const { id } = req.query; - const userId = +id; + const { id: accountUuid } = req.query; - if (!currentUserIsAdmin && userId !== currentUserId) { + if (!(await allowQuery(req, TYPE_ACCOUNT))) { return unauthorized(res); } if (req.method === 'POST') { - const account = await getAccountById(userId); + const account = await getAccount({ accountUuid }); if (!checkPassword(current_password, account.password)) { return badRequest(res, 'Current password is incorrect'); @@ -30,7 +30,7 @@ export default async (req, res) => { const password = hashPassword(new_password); - const updated = await updateAccount(userId, { password }); + const updated = await updateAccount({ password }, { accountUuid }); return ok(res, updated); } diff --git a/pages/api/accounts/index.js b/pages/api/accounts/index.js index aa52ca55..5addd63a 100644 --- a/pages/api/accounts/index.js +++ b/pages/api/accounts/index.js @@ -1,7 +1,7 @@ import { ok, unauthorized, methodNotAllowed, badRequest, hashPassword } from 'next-basics'; import { useAuth } from 'lib/middleware'; import { uuid } from 'lib/crypto'; -import { createAccount, getAccountByUsername, getAccounts } from 'queries'; +import { createAccount, getAccount, getAccounts } from 'queries'; export default async (req, res) => { await useAuth(req, res); @@ -21,7 +21,7 @@ export default async (req, res) => { if (req.method === 'POST') { const { username, password, account_uuid } = req.body; - const accountByUsername = await getAccountByUsername(username); + const accountByUsername = await getAccount({ username }); if (accountByUsername) { return badRequest(res, 'Account already exists'); diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js index d5379a22..aa4803d8 100644 --- a/pages/api/auth/login.js +++ b/pages/api/auth/login.js @@ -1,5 +1,5 @@ import { ok, unauthorized, badRequest, checkPassword, createSecureToken } from 'next-basics'; -import { getAccountByUsername } from 'queries/admin/account/getAccountByUsername'; +import { getAccount } from 'queries'; import { secret } from 'lib/crypto'; export default async (req, res) => { @@ -9,7 +9,7 @@ export default async (req, res) => { return badRequest(res); } - const account = await getAccountByUsername(username); + const account = await getAccount({ username }); if (account && checkPassword(password, account.password)) { const { id, username, isAdmin, accountUuid } = account; diff --git a/pages/api/share/[id].js b/pages/api/share/[id].js index a89829fa..4ff6b81c 100644 --- a/pages/api/share/[id].js +++ b/pages/api/share/[id].js @@ -1,4 +1,4 @@ -import { getWebsiteByShareId } from 'queries'; +import { getWebsite } from 'queries'; import { ok, notFound, methodNotAllowed, createToken } from 'next-basics'; import { secret } from 'lib/crypto'; @@ -6,7 +6,7 @@ export default async (req, res) => { const { id } = req.query; if (req.method === 'GET') { - const website = await getWebsiteByShareId(id); + const website = await getWebsite({ shareId: id }); if (website) { const { websiteUuid } = website; diff --git a/pages/api/websites/[id]/active.js b/pages/api/websites/[id]/active.js index 10e73ea8..59af938e 100644 --- a/pages/api/websites/[id]/active.js +++ b/pages/api/websites/[id]/active.js @@ -2,13 +2,14 @@ import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { getActiveVisitors } from 'queries'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/eventdata.js b/pages/api/websites/[id]/eventdata.js index 86a17b77..0e6ad2e9 100644 --- a/pages/api/websites/[id]/eventdata.js +++ b/pages/api/websites/[id]/eventdata.js @@ -3,13 +3,14 @@ import { getEventData } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); if (req.method === 'POST') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/events.js b/pages/api/websites/[id]/events.js index 192e284a..da88794e 100644 --- a/pages/api/websites/[id]/events.js +++ b/pages/api/websites/[id]/events.js @@ -3,6 +3,7 @@ import { getEventMetrics } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; const unitTypes = ['year', 'month', 'hour', 'day']; @@ -11,7 +12,7 @@ export default async (req, res) => { await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/index.js b/pages/api/websites/[id]/index.js index d802982a..539a3288 100644 --- a/pages/api/websites/[id]/index.js +++ b/pages/api/websites/[id]/index.js @@ -2,19 +2,20 @@ 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 { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); - const { id: websiteId } = req.query; + const { id: websiteUuid } = req.query; - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } if (req.method === 'GET') { - const website = await getWebsite({ websiteUuid: websiteId }); + const website = await getWebsite({ websiteUuid }); return ok(res, website); } @@ -32,7 +33,7 @@ export default async (req, res) => { } } - const website = await getWebsite({ websiteUuid: websiteId }); + const website = await getWebsite({ websiteUuid }); const newShareId = enableShareUrl ? website.shareId || getRandomChars(8) : null; @@ -44,7 +45,7 @@ export default async (req, res) => { shareId: shareId ? shareId : newShareId, userId: account ? account.id : +owner || undefined, }, - { websiteUuid: websiteId }, + { websiteUuid }, ); } catch (e) { if (e.message.includes('Unique constraint') && e.message.includes('share_id')) { @@ -56,11 +57,11 @@ export default async (req, res) => { } if (req.method === 'DELETE') { - if (!(await allowQuery(req, true))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } - await deleteWebsite(websiteId); + await deleteWebsite(websiteUuid); return ok(res); } diff --git a/pages/api/websites/[id]/metrics.js b/pages/api/websites/[id]/metrics.js index e0eab028..0aafe7d3 100644 --- a/pages/api/websites/[id]/metrics.js +++ b/pages/api/websites/[id]/metrics.js @@ -1,8 +1,8 @@ import { allowQuery } from 'lib/auth'; -import { FILTER_IGNORED } from 'lib/constants'; +import { FILTER_IGNORED, TYPE_WEBSITE } from 'lib/constants'; import { useAuth, useCors } from 'lib/middleware'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { getPageviewMetrics, getSessionMetrics, getWebsiteByUuid } from 'queries'; +import { getPageviewMetrics, getSessionMetrics, getWebsite } from 'queries'; const sessionColumns = ['browser', 'os', 'device', 'screen', 'country', 'language']; const pageviewColumns = ['url', 'referrer', 'query']; @@ -38,7 +38,7 @@ export default async (req, res) => { await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } @@ -94,7 +94,7 @@ export default async (req, res) => { let domain; if (type === 'referrer') { - const website = await getWebsiteByUuid(websiteId); + const website = await getWebsite({ websiteUuid: websiteId }); if (!website) { return badRequest(res); diff --git a/pages/api/websites/[id]/pageviews.js b/pages/api/websites/[id]/pageviews.js index 9e05417b..5b628e3a 100644 --- a/pages/api/websites/[id]/pageviews.js +++ b/pages/api/websites/[id]/pageviews.js @@ -3,6 +3,7 @@ import { getPageviewStats } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; const unitTypes = ['year', 'month', 'hour', 'day']; @@ -11,7 +12,7 @@ export default async (req, res) => { await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/reset.js b/pages/api/websites/[id]/reset.js index fe527ad4..0dde02df 100644 --- a/pages/api/websites/[id]/reset.js +++ b/pages/api/websites/[id]/reset.js @@ -2,6 +2,7 @@ import { resetWebsite } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); @@ -10,7 +11,7 @@ export default async (req, res) => { const { id: websiteId } = req.query; if (req.method === 'POST') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/stats.js b/pages/api/websites/[id]/stats.js index 596ebc90..2c5b0156 100644 --- a/pages/api/websites/[id]/stats.js +++ b/pages/api/websites/[id]/stats.js @@ -2,13 +2,14 @@ import { getWebsiteStats } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/queries/admin/account/getAccountById.js b/queries/admin/account/getAccountById.js deleted file mode 100644 index eee1b76a..00000000 --- a/queries/admin/account/getAccountById.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getAccountById(userId) { - return prisma.client.account.findUnique({ - where: { - id: userId, - }, - }); -} diff --git a/queries/admin/account/getAccountByUsername.js b/queries/admin/account/getAccountByUsername.js deleted file mode 100644 index ff64c8ce..00000000 --- a/queries/admin/account/getAccountByUsername.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getAccountByUsername(username) { - return prisma.client.account.findUnique({ - where: { - username, - }, - }); -} diff --git a/queries/admin/website/deleteWebsite.js b/queries/admin/website/deleteWebsite.js index b5e2ff76..f08dc63e 100644 --- a/queries/admin/website/deleteWebsite.js +++ b/queries/admin/website/deleteWebsite.js @@ -1,27 +1,24 @@ import prisma from 'lib/prisma'; import redis, { DELETED } from 'lib/redis'; -import { getWebsiteByUuid } from 'queries'; -export async function deleteWebsite(websiteId) { +export async function deleteWebsite(websiteUuid) { const { client, transaction } = prisma; - const { websiteUuid } = await getWebsiteByUuid(websiteId); - return transaction([ client.pageview.deleteMany({ - where: { session: { website: { websiteUuid: websiteId } } }, + where: { session: { website: { websiteUuid } } }, }), client.eventData.deleteMany({ - where: { event: { session: { website: { websiteUuid: websiteId } } } }, + where: { event: { session: { website: { websiteUuid } } } }, }), client.event.deleteMany({ - where: { session: { website: { websiteUuid: websiteId } } }, + where: { session: { website: { websiteUuid } } }, }), client.session.deleteMany({ - where: { website: { websiteUuid: websiteId } }, + where: { website: { websiteUuid } }, }), client.website.delete({ - where: { websiteUuid: websiteId }, + where: { websiteUuid }, }), ]).then(async res => { if (redis.client) { diff --git a/queries/admin/website/getWebsite.js b/queries/admin/website/getWebsite.js index 83c3e83a..d33c9ead 100644 --- a/queries/admin/website/getWebsite.js +++ b/queries/admin/website/getWebsite.js @@ -1,7 +1,16 @@ import prisma from 'lib/prisma'; +import redis from 'lib/redis'; export async function getWebsite(where) { - return prisma.client.website.findUnique({ - where, - }); + return prisma.client.website + .findUnique({ + where, + }) + .then(async data => { + if (redis.enabled && data) { + await redis.client.set(`website:${data.websiteUuid}`, data.id); + } + + return data; + }); } diff --git a/queries/admin/website/getWebsiteById.js b/queries/admin/website/getWebsiteById.js deleted file mode 100644 index f486bd8f..00000000 --- a/queries/admin/website/getWebsiteById.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getWebsiteById(websiteId) { - return prisma.client.website.findUnique({ - where: { - id: websiteId, - }, - }); -} diff --git a/queries/admin/website/getWebsiteByShareId.js b/queries/admin/website/getWebsiteByShareId.js deleted file mode 100644 index bfc99ce7..00000000 --- a/queries/admin/website/getWebsiteByShareId.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getWebsiteByShareId(shareId) { - return prisma.client.website.findUnique({ - where: { - shareId, - }, - }); -} diff --git a/queries/admin/website/getWebsiteByUuid.js b/queries/admin/website/getWebsiteByUuid.js deleted file mode 100644 index 158d357a..00000000 --- a/queries/admin/website/getWebsiteByUuid.js +++ /dev/null @@ -1,18 +0,0 @@ -import prisma from 'lib/prisma'; -import redis from 'lib/redis'; - -export async function getWebsiteByUuid(websiteUuid) { - return prisma.client.website - .findUnique({ - where: { - websiteUuid, - }, - }) - .then(async res => { - if (redis.client && res) { - await redis.client.set(`website:${res.websiteUuid}`, res.id); - } - - return res; - }); -} diff --git a/queries/index.js b/queries/index.js index abff147a..a570af65 100644 --- a/queries/index.js +++ b/queries/index.js @@ -1,8 +1,6 @@ export * from './admin/account/createAccount'; export * from './admin/account/deleteAccount'; export * from './admin/account/getAccount'; -export * from './admin/account/getAccountById'; -export * from './admin/account/getAccountByUsername'; export * from './admin/account/getAccounts'; export * from './admin/account/updateAccount'; export * from './admin/website/createWebsite'; @@ -10,9 +8,6 @@ export * from './admin/website/deleteWebsite'; export * from './admin/website/getAllWebsites'; export * from './admin/website/getUserWebsites'; export * from './admin/website/getWebsite'; -export * from './admin/website/getWebsiteById'; -export * from './admin/website/getWebsiteByShareId'; -export * from './admin/website/getWebsiteByUuid'; export * from './admin/website/resetWebsite'; export * from './admin/website/updateWebsite'; export * from './analytics/event/getEventMetrics'; From 46a18f2918e4a9aa58ff16d9181ba4a069b46449 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 25 Oct 2022 11:01:28 -0700 Subject: [PATCH 4/9] Fixed account lookup. --- lib/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/auth.js b/lib/auth.js index f43f2002..93027544 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -56,7 +56,7 @@ export async function allowQuery(req, type) { } else if (type === TYPE_ACCOUNT) { const account = await getAccount({ accountUuid: id }); - return account && account.id === id; + return account && account.accountUuid === id; } } From 8b64ef1a4e84ce004807c550c53c87eede7559e7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 25 Oct 2022 15:48:49 -0700 Subject: [PATCH 5/9] Fixed issue with accessing user dashboards. Closes #1590 --- components/pages/Dashboard.js | 6 +----- components/settings/AccountSettings.js | 16 +++++++++------- pages/api/accounts/index.js | 4 ++-- pages/api/realtime/init.js | 2 +- pages/api/websites/index.js | 9 +++++---- pages/dashboard/[[...id]].js | 14 ++++++++++++-- queries/admin/account/getAccounts.js | 1 + queries/admin/website/getUserWebsites.js | 6 ++---- 8 files changed, 33 insertions(+), 25 deletions(-) diff --git a/components/pages/Dashboard.js b/components/pages/Dashboard.js index bd6a8b3a..70a4d624 100644 --- a/components/pages/Dashboard.js +++ b/components/pages/Dashboard.js @@ -1,6 +1,5 @@ import { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useRouter } from 'next/router'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import WebsiteList from 'components/pages/WebsiteList'; @@ -16,10 +15,7 @@ const messages = defineMessages({ more: { id: 'label.more', defaultMessage: 'More' }, }); -export default function Dashboard() { - const router = useRouter(); - const { id } = router.query; - const userId = id?.[0]; +export default function Dashboard({ userId }) { const dashboard = useDashboard(); const { showCharts, limit, editing } = dashboard; const [max, setMax] = useState(limit); diff --git a/components/settings/AccountSettings.js b/components/settings/AccountSettings.js index 2ec378f1..eebdae40 100644 --- a/components/settings/AccountSettings.js +++ b/components/settings/AccountSettings.js @@ -29,13 +29,15 @@ export default function AccountSettings() { const Checkmark = ({ isAdmin }) => (isAdmin ? } size="medium" /> : null); - const DashboardLink = row => ( - - - } /> - - - ); + const DashboardLink = row => { + return ( + + + } /> + + + ); + }; const Buttons = row => ( diff --git a/pages/api/accounts/index.js b/pages/api/accounts/index.js index 5addd63a..22248ed4 100644 --- a/pages/api/accounts/index.js +++ b/pages/api/accounts/index.js @@ -21,9 +21,9 @@ export default async (req, res) => { if (req.method === 'POST') { const { username, password, account_uuid } = req.body; - const accountByUsername = await getAccount({ username }); + const account = await getAccount({ username }); - if (accountByUsername) { + if (account) { return badRequest(res, 'Account already exists'); } diff --git a/pages/api/realtime/init.js b/pages/api/realtime/init.js index a7e175a5..9a9a4297 100644 --- a/pages/api/realtime/init.js +++ b/pages/api/realtime/init.js @@ -10,7 +10,7 @@ export default async (req, res) => { if (req.method === 'GET') { const { userId } = req.auth; - const websites = await getUserWebsites(userId); + const websites = await getUserWebsites({ userId }); const ids = websites.map(({ websiteUuid }) => websiteUuid); const token = createToken({ websites: ids }, secret()); const data = await getRealtimeData(ids, subMinutes(new Date(), 30)); diff --git a/pages/api/websites/index.js b/pages/api/websites/index.js index 9fade8c2..36d38660 100644 --- a/pages/api/websites/index.js +++ b/pages/api/websites/index.js @@ -6,15 +6,16 @@ import { uuid } from 'lib/crypto'; export default async (req, res) => { await useAuth(req, res); - const { userId: currentUserId, isAdmin, accountUuid } = req.auth; 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: accountUuid }); + account = await getAccount({ accountUuid }); } - const userId = account ? account.id : +user_id; + const userId = account ? account.id : user_id; if (req.method === 'GET') { if (userId && userId !== currentUserId && !isAdmin) { @@ -24,7 +25,7 @@ export default async (req, res) => { const websites = isAdmin && include_all ? await getAllWebsites() - : await getUserWebsites(userId || currentUserId); + : await getUserWebsites({ userId: account.id }); return ok(res, websites); } diff --git a/pages/dashboard/[[...id]].js b/pages/dashboard/[[...id]].js index 452a425e..7c762097 100644 --- a/pages/dashboard/[[...id]].js +++ b/pages/dashboard/[[...id]].js @@ -2,17 +2,27 @@ import React from 'react'; 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'; export default function DashboardPage() { + const { + query: { id }, + isReady, + asPath, + } = useRouter(); const { loading } = useRequireLogin(); + const user = useUser(); - if (loading) { + if (!user || !isReady || loading) { return null; } + const userId = id?.[0]; + return ( - + ); } diff --git a/queries/admin/account/getAccounts.js b/queries/admin/account/getAccounts.js index 71d5e4c5..ceca2582 100644 --- a/queries/admin/account/getAccounts.js +++ b/queries/admin/account/getAccounts.js @@ -14,6 +14,7 @@ export async function getAccounts() { isAdmin: true, createdAt: true, updatedAt: true, + accountUuid: true, }, }); } diff --git a/queries/admin/website/getUserWebsites.js b/queries/admin/website/getUserWebsites.js index c1a9d559..9a725ec2 100644 --- a/queries/admin/website/getUserWebsites.js +++ b/queries/admin/website/getUserWebsites.js @@ -1,10 +1,8 @@ import prisma from 'lib/prisma'; -export async function getUserWebsites(userId) { +export async function getUserWebsites(where) { return prisma.client.website.findMany({ - where: { - userId, - }, + where, orderBy: { name: 'asc', }, From 7f3db334f5e476e4dd7b723097bc4503efb8b8fe Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Tue, 25 Oct 2022 16:48:39 -0700 Subject: [PATCH 6/9] fix sort on dashboard (#1600) --- components/pages/DashboardEdit.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/pages/DashboardEdit.js b/components/pages/DashboardEdit.js index 38b1d7d6..0cc6d4e7 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.websiteId) })) + .map(website => ({ ...website, order: order.indexOf(website.websiteUuid) })) .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?.websiteId || 0)); + setOrder(orderedWebsites.map(website => website?.websiteUuid || 0)); } function handleSave() { @@ -76,8 +76,12 @@ export default function DashboardEdit({ websites }) { ref={provided.innerRef} style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }} > - {ordered.map(({ websiteId, name, domain }, index) => ( - + {ordered.map(({ websiteUuid, name, domain }, index) => ( + {(provided, snapshot) => (
Date: Tue, 25 Oct 2022 20:17:13 -0700 Subject: [PATCH 7/9] Refactor redis calls. --- pages/api/websites/index.js | 2 +- queries/admin/account/deleteAccount.js | 2 +- queries/admin/website/createWebsite.js | 4 ++-- queries/admin/website/deleteWebsite.js | 4 ++-- queries/admin/website/getWebsite.js | 2 +- queries/analytics/session/createSession.js | 8 ++++---- queries/analytics/session/getSessionByUuid.js | 8 ++++---- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pages/api/websites/index.js b/pages/api/websites/index.js index 36d38660..daecac88 100644 --- a/pages/api/websites/index.js +++ b/pages/api/websites/index.js @@ -25,7 +25,7 @@ export default async (req, res) => { const websites = isAdmin && include_all ? await getAllWebsites() - : await getUserWebsites({ userId: account.id }); + : await getUserWebsites({ userId: account?.id }); return ok(res, websites); } diff --git a/queries/admin/account/deleteAccount.js b/queries/admin/account/deleteAccount.js index caef8709..9f6dd636 100644 --- a/queries/admin/account/deleteAccount.js +++ b/queries/admin/account/deleteAccount.js @@ -39,7 +39,7 @@ export async function deleteAccount(userId) { }), ]) .then(async res => { - if (redis.client) { + if (redis.enabled) { for (let i = 0; i < websiteUuids.length; i++) { await redis.set(`website:${websiteUuids[i]}`, DELETED); } diff --git a/queries/admin/website/createWebsite.js b/queries/admin/website/createWebsite.js index 7a45f1cf..22ac50c6 100644 --- a/queries/admin/website/createWebsite.js +++ b/queries/admin/website/createWebsite.js @@ -14,8 +14,8 @@ export async function createWebsite(userId, data) { }, }) .then(async res => { - if (redis.client && res) { - await redis.client.set(`website:${res.websiteUuid}`, res.id); + if (redis.enabled && res) { + await redis.set(`website:${res.websiteUuid}`, res.id); } return res; diff --git a/queries/admin/website/deleteWebsite.js b/queries/admin/website/deleteWebsite.js index f08dc63e..4eb4d97b 100644 --- a/queries/admin/website/deleteWebsite.js +++ b/queries/admin/website/deleteWebsite.js @@ -21,8 +21,8 @@ export async function deleteWebsite(websiteUuid) { where: { websiteUuid }, }), ]).then(async res => { - if (redis.client) { - await redis.client.set(`website:${websiteUuid}`, DELETED); + if (redis.enabled) { + await redis.set(`website:${websiteUuid}`, DELETED); } return res; diff --git a/queries/admin/website/getWebsite.js b/queries/admin/website/getWebsite.js index d33c9ead..6c109ff7 100644 --- a/queries/admin/website/getWebsite.js +++ b/queries/admin/website/getWebsite.js @@ -8,7 +8,7 @@ export async function getWebsite(where) { }) .then(async data => { if (redis.enabled && data) { - await redis.client.set(`website:${data.websiteUuid}`, data.id); + await redis.set(`website:${data.websiteUuid}`, data.id); } return data; diff --git a/queries/analytics/session/createSession.js b/queries/analytics/session/createSession.js index c851eb0a..824ed252 100644 --- a/queries/analytics/session/createSession.js +++ b/queries/analytics/session/createSession.js @@ -30,8 +30,8 @@ async function relationalQuery(websiteId, data) { }, }) .then(async res => { - if (redis.client && res) { - await redis.client.set(`session:${res.sessionUuid}`, 1); + if (redis.enabled && res) { + await redis.set(`session:${res.sessionUuid}`, 1); } return res; @@ -59,7 +59,7 @@ async function clickhouseQuery( await sendMessage(params, 'event'); - if (redis.client) { - await redis.client.set(`session:${sessionUuid}`, 1); + if (redis.enabled) { + await redis.set(`session:${sessionUuid}`, 1); } } diff --git a/queries/analytics/session/getSessionByUuid.js b/queries/analytics/session/getSessionByUuid.js index 638891cb..2bb29c01 100644 --- a/queries/analytics/session/getSessionByUuid.js +++ b/queries/analytics/session/getSessionByUuid.js @@ -18,8 +18,8 @@ async function relationalQuery(sessionUuid) { }, }) .then(async res => { - if (redis.client && res) { - await redis.client.set(`session:${res.sessionUuid}`, 1); + if (redis.enabled && res) { + await redis.set(`session:${res.sessionUuid}`, 1); } return res; @@ -48,8 +48,8 @@ async function clickhouseQuery(sessionUuid) { ) .then(result => findFirst(result)) .then(async res => { - if (redis.client && res) { - await redis.client.set(`session:${res.session_uuid}`, 1); + if (redis.enabled && res) { + await redis.set(`session:${res.session_uuid}`, 1); } return res; From 1d6f158509c870e9d79c42e3084a79b5f6dc487c Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Wed, 26 Oct 2022 11:36:58 -0700 Subject: [PATCH 8/9] fix id/uuid --- components/forms/WebsiteEditForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/forms/WebsiteEditForm.js b/components/forms/WebsiteEditForm.js index b56e21c1..f13d0aa0 100644 --- a/components/forms/WebsiteEditForm.js +++ b/components/forms/WebsiteEditForm.js @@ -78,7 +78,7 @@ export default function WebsiteEditForm({ values, onSave, onClose }) { const [message, setMessage] = useState(); const handleSubmit = async values => { - const { id: websiteId } = values; + const { websiteUuid: websiteId } = values; const { ok, data } = await post(websiteId ? `/websites/${websiteId}` : '/websites', values); From 34791c3f146b669c76f2c931f4d6af8eaaff81ef Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Wed, 26 Oct 2022 15:56:37 -0700 Subject: [PATCH 9/9] bump version 1.39.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e586774f..a09b4e15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "1.39.2", + "version": "1.39.3", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT",