diff --git a/components/common/ConfirmDeleteForm.js b/components/common/ConfirmDeleteForm.js new file mode 100644 index 00000000..3496a305 --- /dev/null +++ b/components/common/ConfirmDeleteForm.js @@ -0,0 +1,29 @@ +import { useState } from 'react'; +import { Button, LoadingButton, Form, FormButtons } from 'react-basics'; +import useMessages from 'hooks/useMessages'; + +export function ConfirmDeleteForm({ name, onConfirm, onClose }) { + const [loading, setLoading] = useState(false); + const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + + const handleConfirm = () => { + setLoading(true); + onConfirm(); + }; + + return ( +
+

+ {name} }} /> +

+ + + {formatMessage(labels.delete)} + + + +
+ ); +} + +export default ConfirmDeleteForm; diff --git a/components/common/LinkButton.js b/components/common/LinkButton.js new file mode 100644 index 00000000..8c050147 --- /dev/null +++ b/components/common/LinkButton.js @@ -0,0 +1,12 @@ +import Link from 'next/link'; +import { Icon, Icons, Text } from 'react-basics'; +import styles from './LinkButton.module.css'; + +export default function LinkButton({ href, icon, children }) { + return ( + + {icon || } + {children} + + ); +} diff --git a/components/common/LinkButton.module.css b/components/common/LinkButton.module.css new file mode 100644 index 00000000..ae8a3b62 --- /dev/null +++ b/components/common/LinkButton.module.css @@ -0,0 +1,28 @@ +.button { + display: flex; + align-items: center; + align-self: flex-start; + white-space: nowrap; + gap: var(--size200); + font-family: inherit; + color: var(--base900); + background: var(--base100); + border: 1px solid transparent; + border-radius: var(--border-radius); + min-height: var(--base-height); + padding: 0 var(--size600); + position: relative; + cursor: pointer; +} + +.button:hover { + background: var(--base200); +} + +.button:active { + background: var(--base300); +} + +.button:visited { + color: var(--base900); +} diff --git a/components/pages/reports/ReportsTable.js b/components/pages/reports/ReportsTable.js index bcc97204..244740e1 100644 --- a/components/pages/reports/ReportsTable.js +++ b/components/pages/reports/ReportsTable.js @@ -1,9 +1,12 @@ -import Link from 'next/link'; -import { Button, Text, Icon, Icons } from 'react-basics'; +import { useState } from 'react'; +import { Flexbox, Icon, Icons, Text, Button, Modal } from 'react-basics'; +import LinkButton from 'components/common/LinkButton'; import SettingsTable from 'components/common/SettingsTable'; -import useMessages from 'hooks/useMessages'; +import ConfirmDeleteForm from 'components/common/ConfirmDeleteForm'; +import { useMessages } from 'hooks'; -export function ReportsTable({ data = [] }) { +export function ReportsTable({ data = [], onDelete = () => {} }) { + const [report, setReport] = useState(null); const { formatMessage, labels } = useMessages(); const columns = [ @@ -13,23 +16,39 @@ export function ReportsTable({ data = [] }) { { name: 'action', label: ' ' }, ]; - return ( - - {row => { - const { id } = row; + const handleConfirm = () => { + onDelete(report.id); + }; - return ( - - - - ); - }} - + return ( + <> + + {row => { + const { id } = row; + + return ( + + {formatMessage(labels.view)} + + + ); + }} + + {report && ( + + setReport(null)} + /> + + )} + ); } diff --git a/components/pages/websites/WebsiteReportsPage.js b/components/pages/websites/WebsiteReportsPage.js index b6f41bac..56927028 100644 --- a/components/pages/websites/WebsiteReportsPage.js +++ b/components/pages/websites/WebsiteReportsPage.js @@ -7,7 +7,11 @@ import WebsiteHeader from './WebsiteHeader'; export function WebsiteReportsPage({ websiteId }) { const { formatMessage, labels } = useMessages(); - const { reports, error, isLoading } = useReports(websiteId); + const { reports, error, isLoading, deleteReport } = useReports(websiteId); + + const handleDelete = async id => { + await deleteReport(id); + }; return ( @@ -22,7 +26,7 @@ export function WebsiteReportsPage({ websiteId }) { - + ); } diff --git a/hooks/useReports.js b/hooks/useReports.js index 90aa5cf5..f4369eec 100644 --- a/hooks/useReports.js +++ b/hooks/useReports.js @@ -1,10 +1,23 @@ +import { useState } from 'react'; import useApi from './useApi'; export function useReports(websiteId) { - const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery(['reports'], () => get(`/reports`, { websiteId })); + const [modified, setModified] = useState(Date.now()); + const { get, useQuery, del, useMutation } = useApi(); + const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); + const { data, error, isLoading } = useQuery(['reports:website', { websiteId, modified }], () => + get(`/reports`, { websiteId }), + ); - return { reports: data, error, isLoading }; + const deleteReport = id => { + mutate(id, { + onSuccess: () => { + setModified(Date.now()); + }, + }); + }; + + return { reports: data, error, isLoading, deleteReport }; } export default useReports; diff --git a/lib/auth.ts b/lib/auth.ts index 04585943..10f7fbca 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -2,16 +2,9 @@ import { Report } from '@prisma/client'; import redis from '@umami/redis-client'; import debug from 'debug'; import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants'; -import { secret, isUuid } from 'lib/crypto'; -import { - createSecureToken, - ensureArray, - getRandomChars, - parseSecureToken, - parseToken, -} from 'next-basics'; -import { getTeamUser } from 'queries'; -import { getTeamWebsite, getTeamWebsiteByTeamMemberId } from 'queries/admin/teamWebsite'; +import { secret } from 'lib/crypto'; +import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics'; +import { getTeamUser, getTeamWebsite, findTeamWebsiteByUserId } from 'queries'; import { loadWebsite } from './load'; import { Auth } from './types'; @@ -37,15 +30,6 @@ export function getAuthToken(req) { } } -export function parseAuthToken(req) { - try { - return parseSecureToken(getAuthToken(req), secret()); - } catch (e) { - log(e); - return null; - } -} - export function parseShareToken(req) { try { return parseToken(req.headers[SHARE_TOKEN_HEADER], secret()); @@ -55,21 +39,6 @@ export function parseShareToken(req) { } } -export function isValidToken(token, validation) { - try { - if (typeof validation === 'object') { - return !Object.keys(validation).find(key => token[key] !== validation[key]); - } else if (typeof validation === 'function') { - return validation(token); - } - } catch (e) { - log(e); - return false; - } - - return false; -} - export async function canViewWebsite({ user, shareToken }: Auth, websiteId: string) { if (user?.isAdmin) { return true; @@ -79,19 +48,13 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri return true; } - const teamWebsite = await getTeamWebsiteByTeamMemberId(websiteId, user.id); + const website = await loadWebsite(websiteId); - if (teamWebsite) { + if (user.id === website?.userId) { return true; } - const website = await loadWebsite(websiteId); - - if (website.userId) { - return user.id === website.userId; - } - - return false; + return !!(await findTeamWebsiteByUserId(websiteId, user.id)); } export async function canCreateWebsite({ user }: Auth) { @@ -107,17 +70,9 @@ export async function canUpdateWebsite({ user }: Auth, websiteId: string) { return true; } - if (!isUuid(websiteId)) { - return false; - } - const website = await loadWebsite(websiteId); - if (website.userId) { - return user.id === website.userId; - } - - return false; + return user.id === website?.userId; } export async function canDeleteWebsite({ user }: Auth, websiteId: string) { @@ -127,11 +82,7 @@ export async function canDeleteWebsite({ user }: Auth, websiteId: string) { const website = await loadWebsite(websiteId); - if (website.userId) { - return user.id === website.userId; - } - - return false; + return user.id === website?.userId; } export async function canViewReport(auth: Auth, report: Report) { @@ -139,27 +90,23 @@ export async function canViewReport(auth: Auth, report: Report) { return true; } - if ((auth.user.id = report.userId)) { + if (auth.user.id == report.userId) { return true; } - if (await canViewWebsite(auth, report.websiteId)) { - return true; - } - - return false; + return !!(await canViewWebsite(auth, report.websiteId)); } -export async function canUpdateReport(auth: Auth, report: Report) { - if (auth.user.isAdmin) { +export async function canUpdateReport({ user }: Auth, report: Report) { + if (user.isAdmin) { return true; } - if ((auth.user.id = report.userId)) { - return true; - } + return user.id == report.userId; +} - return false; +export async function canDeleteReport(auth: Auth, report: Report) { + return canUpdateReport(auth, report); } export async function canCreateTeam({ user }: Auth) { @@ -183,13 +130,9 @@ export async function canUpdateTeam({ user }: Auth, teamId: string) { return true; } - if (isUuid(teamId)) { - const teamUser = await getTeamUser(teamId, user.id); + const teamUser = await getTeamUser(teamId, user.id); - return hasPermission(teamUser.role, PERMISSIONS.teamUpdate); - } - - return false; + return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamUpdate); } export async function canDeleteTeam({ user }: Auth, teamId: string) { @@ -197,13 +140,9 @@ export async function canDeleteTeam({ user }: Auth, teamId: string) { return true; } - if (isUuid(teamId)) { - const teamUser = await getTeamUser(teamId, user.id); + const teamUser = await getTeamUser(teamId, user.id); - return hasPermission(teamUser.role, PERMISSIONS.teamDelete); - } - - return false; + return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamDelete); } export async function canDeleteTeamUser({ user }: Auth, teamId: string, removeUserId: string) { @@ -211,17 +150,13 @@ export async function canDeleteTeamUser({ user }: Auth, teamId: string, removeUs return true; } - if (isUuid(teamId) && isUuid(removeUserId)) { - if (removeUserId === user.id) { - return true; - } - - const teamUser = await getTeamUser(teamId, user.id); - - return hasPermission(teamUser.role, PERMISSIONS.teamUpdate); + if (removeUserId === user.id) { + return true; } - return false; + const teamUser = await getTeamUser(teamId, user.id); + + return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamUpdate); } export async function canDeleteTeamWebsite({ user }: Auth, teamId: string, websiteId: string) { @@ -229,13 +164,13 @@ export async function canDeleteTeamWebsite({ user }: Auth, teamId: string, websi return true; } - if (isUuid(teamId) && isUuid(websiteId)) { - const teamWebsite = await getTeamWebsite(teamId, websiteId); + const teamWebsite = await getTeamWebsite(teamId, websiteId); - if (teamWebsite.website.userId === user.id) { - return true; - } + if (teamWebsite?.website?.userId === user.id) { + return true; + } + if (teamWebsite) { const teamUser = await getTeamUser(teamWebsite.teamId, user.id); return hasPermission(teamUser.role, PERMISSIONS.teamUpdate); diff --git a/lib/cache.ts b/lib/cache.ts index 7ee7bf28..bc46c23d 100644 --- a/lib/cache.ts +++ b/lib/cache.ts @@ -1,11 +1,11 @@ import { User, Website } from '@prisma/client'; import redis from '@umami/redis-client'; -import { getSession, getUser, getWebsite } from '../queries'; +import { getSession, getUserById, getWebsiteById } from '../queries'; const { fetchObject, storeObject, deleteObject } = redis; async function fetchWebsite(id): Promise { - return fetchObject(`website:${id}`, () => getWebsite({ id })); + return fetchObject(`website:${id}`, () => getWebsiteById(id)); } async function storeWebsite(data) { @@ -20,7 +20,7 @@ async function deleteWebsite(id) { } async function fetchUser(id): Promise { - return fetchObject(`user:${id}`, () => getUser({ id }, { includePassword: true })); + return fetchObject(`user:${id}`, () => getUserById(id, { includePassword: true })); } async function storeUser(data) { @@ -35,7 +35,7 @@ async function deleteUser(id) { } async function fetchSession(id) { - return fetchObject(`session:${id}`, () => getSession({ id })); + return fetchObject(`session:${id}`, () => getSession(id)); } async function storeSession(data) { diff --git a/lib/dynamicData.ts b/lib/data.ts similarity index 100% rename from lib/dynamicData.ts rename to lib/data.ts diff --git a/lib/load.ts b/lib/load.ts index 4ce18b09..d980f8e9 100644 --- a/lib/load.ts +++ b/lib/load.ts @@ -1,5 +1,5 @@ import cache from 'lib/cache'; -import { getWebsite, getSession, getUser } from 'queries'; +import { getSession, getUserById, getWebsiteById } from 'queries'; import { User, Website, Session } from '@prisma/client'; export async function loadWebsite(websiteId: string): Promise { @@ -8,7 +8,7 @@ export async function loadWebsite(websiteId: string): Promise { if (cache.enabled) { website = await cache.fetchWebsite(websiteId); } else { - website = await getWebsite({ id: websiteId }); + website = await getWebsiteById(websiteId); } if (!website || website.deletedAt) { @@ -24,7 +24,7 @@ export async function loadSession(sessionId: string): Promise { if (cache.enabled) { session = await cache.fetchSession(sessionId); } else { - session = await getSession({ id: sessionId }); + session = await getSession(sessionId); } if (!session) { @@ -40,7 +40,7 @@ export async function loadUser(userId: string): Promise { if (cache.enabled) { user = await cache.fetchUser(userId); } else { - user = await getUser({ id: userId }); + user = await getUserById(userId); } if (!user || user.deletedAt) { diff --git a/lib/middleware.ts b/lib/middleware.ts index bcab71ef..414cab23 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -12,7 +12,7 @@ import { findSession } from 'lib/session'; import { getAuthToken, parseShareToken } from 'lib/auth'; import { secret, isUuid } from 'lib/crypto'; import { ROLES } from 'lib/constants'; -import { getUser } from '../queries'; +import { getUserById } from '../queries'; import { NextApiRequestCollect } from 'pages/api/send'; const log = debug('umami:middleware'); @@ -53,7 +53,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { const { userId, authKey } = payload || {}; if (isUuid(userId)) { - user = await getUser({ id: userId }); + user = await getUserById(userId); } else if (redis.enabled && authKey) { user = await redis.get(authKey); } diff --git a/pages/api/auth/login.ts b/pages/api/auth/login.ts index 64c2c26d..af206938 100644 --- a/pages/api/auth/login.ts +++ b/pages/api/auth/login.ts @@ -9,7 +9,7 @@ import { methodNotAllowed, } from 'next-basics'; import redis from '@umami/redis-client'; -import { getUser } from 'queries'; +import { getUserByUsername } from 'queries'; import { secret } from 'lib/crypto'; import { NextApiRequestQueryBody, User } from 'lib/types'; import { setAuthKey } from 'lib/auth'; @@ -37,7 +37,7 @@ export default async ( return badRequest(res); } - const user = await getUser({ username }, { includePassword: true }); + const user = await getUserByUsername(username, { includePassword: true }); if (user && checkPassword(password, user.password)) { if (redis.enabled) { diff --git a/pages/api/me/password.ts b/pages/api/me/password.ts index 70d8de5f..f9f60fc5 100644 --- a/pages/api/me/password.ts +++ b/pages/api/me/password.ts @@ -9,7 +9,7 @@ import { forbidden, ok, } from 'next-basics'; -import { getUser, updateUser } from 'queries'; +import { getUserById, updateUser } from 'queries'; export interface UserPasswordRequestQuery { id: string; @@ -34,7 +34,7 @@ export default async ( const { id } = req.auth.user; if (req.method === 'POST') { - const user = await getUser({ id }, { includePassword: true }); + const user = await getUserById(id, { includePassword: true }); if (!checkPassword(currentPassword, user.password)) { return badRequest(res, 'Current password is incorrect'); diff --git a/pages/api/reports/[id].ts b/pages/api/reports/[id].ts index bcd22b4e..85bc302c 100644 --- a/pages/api/reports/[id].ts +++ b/pages/api/reports/[id].ts @@ -1,9 +1,9 @@ -import { canUpdateReport, canViewReport } from 'lib/auth'; +import { canUpdateReport, canViewReport, canDeleteReport } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { getReportById, updateReport } from 'queries'; +import { getReportById, updateReport, deleteReport } from 'queries'; export interface ReportRequestQuery { id: string; @@ -24,50 +24,55 @@ export default async ( await useCors(req, res); await useAuth(req, res); + const { id: reportId } = req.query; + const { + user: { id: userId }, + } = req.auth; + if (req.method === 'GET') { - const { id: reportId } = req.query; + const report = await getReportById(reportId); - const data = await getReportById(reportId); - - if (!(await canViewReport(req.auth, data))) { + if (!(await canViewReport(req.auth, report))) { return unauthorized(res); } - data.parameters = JSON.parse(data.parameters); + report.parameters = JSON.parse(report.parameters); - return ok(res, data); + return ok(res, report); } if (req.method === 'POST') { - const { id: reportId } = req.query; - const { - user: { id: userId }, - } = req.auth; - const { websiteId, type, name, description, parameters } = req.body; - const data = await getReportById(reportId); + const report = await getReportById(reportId); - if (!(await canUpdateReport(req.auth, data))) { + if (!(await canUpdateReport(req.auth, report))) { return unauthorized(res); } - const result = await updateReport( - { - websiteId, - userId, - type, - name, - description, - parameters: JSON.stringify(parameters), - } as any, - { - id: reportId, - }, - ); + const result = await updateReport(reportId, { + websiteId, + userId, + type, + name, + description, + parameters: JSON.stringify(parameters), + } as any); return ok(res, result); } + if (req.method === 'DELETE') { + const report = await getReportById(reportId); + + if (!(await canDeleteReport(req.auth, report))) { + return unauthorized(res); + } + + await deleteReport(reportId); + + return ok(res); + } + return methodNotAllowed(res); }; diff --git a/pages/api/reports/index.ts b/pages/api/reports/index.ts index fcd8fd37..c856b565 100644 --- a/pages/api/reports/index.ts +++ b/pages/api/reports/index.ts @@ -2,7 +2,7 @@ import { useAuth, useCors } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { createReport, getReports } from 'queries'; +import { createReport, getWebsiteReports } from 'queries'; import { canViewWebsite } from 'lib/auth'; import { uuid } from 'lib/crypto'; @@ -35,7 +35,7 @@ export default async ( return unauthorized(res); } - const data = await getReports({ websiteId }); + const data = await getWebsiteReports(websiteId); return ok(res, data); } diff --git a/pages/api/share/[id].ts b/pages/api/share/[id].ts index b5511f2d..0592d216 100644 --- a/pages/api/share/[id].ts +++ b/pages/api/share/[id].ts @@ -2,7 +2,7 @@ import { NextApiRequestQueryBody } from 'lib/types'; import { secret } from 'lib/crypto'; import { NextApiResponse } from 'next'; import { createToken, methodNotAllowed, notFound, ok } from 'next-basics'; -import { getWebsite } from 'queries'; +import { getWebsiteByShareId } from 'queries'; export interface ShareRequestQuery { id: string; @@ -20,7 +20,7 @@ export default async ( const { id: shareId } = req.query; if (req.method === 'GET') { - const website = await getWebsite({ shareId }); + const website = await getWebsiteByShareId(shareId); if (website) { const data = { websiteId: website.id }; diff --git a/pages/api/teams/[id]/index.ts b/pages/api/teams/[id]/index.ts index 1c7dbb66..7fb664a0 100644 --- a/pages/api/teams/[id]/index.ts +++ b/pages/api/teams/[id]/index.ts @@ -4,7 +4,7 @@ import { canDeleteTeam, canUpdateTeam, canViewTeam } from 'lib/auth'; import { useAuth } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { deleteTeam, getTeam, updateTeam } from 'queries'; +import { deleteTeam, getTeamById, updateTeam } from 'queries'; export interface TeamRequestQuery { id: string; @@ -28,7 +28,7 @@ export default async ( return unauthorized(res); } - const user = await getTeam({ id: teamId }); + const user = await getTeamById(teamId, { includeTeamUser: true }); return ok(res, user); } @@ -41,7 +41,7 @@ export default async ( const { name, accessCode } = req.body; const data = { name, accessCode }; - const updated = await updateTeam(data, { id: teamId }); + const updated = await updateTeam(teamId, data); return ok(res, updated); } diff --git a/pages/api/teams/[id]/users/index.ts b/pages/api/teams/[id]/users/index.ts index af01d0ce..c73da683 100644 --- a/pages/api/teams/[id]/users/index.ts +++ b/pages/api/teams/[id]/users/index.ts @@ -3,7 +3,7 @@ import { useAuth } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { createTeamUser, getTeamUsers, getUser } from 'queries'; +import { createTeamUser, getTeamUsers, getUserByUsername } from 'queries'; export interface TeamUserRequestQuery { id: string; @@ -40,7 +40,7 @@ export default async ( const { email, roleId: roleId } = req.body; // Check for User - const user = await getUser({ username: email }); + const user = await getUserByUsername(email); if (!user) { return badRequest(res, 'The User does not exists.'); diff --git a/pages/api/teams/join.ts b/pages/api/teams/join.ts index 17c9bf32..ce7367a0 100644 --- a/pages/api/teams/join.ts +++ b/pages/api/teams/join.ts @@ -3,7 +3,7 @@ import { NextApiRequestQueryBody } from 'lib/types'; import { useAuth } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, notFound } from 'next-basics'; -import { createTeamUser, getTeam, getTeamUser } from 'queries'; +import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries'; import { ROLES } from 'lib/constants'; export interface TeamsJoinRequestBody { @@ -19,7 +19,7 @@ export default async ( if (req.method === 'POST') { const { accessCode } = req.body; - const team = await getTeam({ accessCode }); + const team = await getTeamByAccessCode(accessCode); if (!team) { return notFound(res, 'message.team-not-found'); diff --git a/pages/api/users/[id]/index.ts b/pages/api/users/[id]/index.ts index a4ab05ff..e09b1b5f 100644 --- a/pages/api/users/[id]/index.ts +++ b/pages/api/users/[id]/index.ts @@ -3,7 +3,7 @@ import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth'; import { useAuth } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { deleteUser, getUser, updateUser } from 'queries'; +import { deleteUser, getUserById, getUserByUsername, updateUser } from 'queries'; export interface UserRequestQuery { id: string; @@ -31,7 +31,7 @@ export default async ( return unauthorized(res); } - const user = await getUser({ id }); + const user = await getUserById(id); return ok(res, user); } @@ -43,7 +43,7 @@ export default async ( const { username, password, role } = req.body; - const user = await getUser({ id }); + const user = await getUserById(id); const data: any = {}; @@ -62,9 +62,9 @@ export default async ( // Check when username changes if (data.username && user.username !== data.username) { - const userByUsername = await getUser({ username }); + const user = await getUserByUsername(username); - if (userByUsername) { + if (user) { return badRequest(res, 'User already exists'); } } diff --git a/pages/api/users/index.ts b/pages/api/users/index.ts index c6103c35..6f6c205f 100644 --- a/pages/api/users/index.ts +++ b/pages/api/users/index.ts @@ -5,7 +5,7 @@ import { useAuth } from 'lib/middleware'; import { NextApiRequestQueryBody, Role, User } from 'lib/types'; import { NextApiResponse } from 'next'; import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { createUser, getUser, getUsers } from 'queries'; +import { createUser, getUserByUsername, getUsers } from 'queries'; export interface UsersRequestBody { username: string; @@ -37,7 +37,7 @@ export default async ( const { username, password, role, id } = req.body; - const existingUser = await getUser({ username }, { showDeleted: true }); + const existingUser = await getUserByUsername(username, { showDeleted: true }); if (existingUser) { return badRequest(res, 'User already exists'); diff --git a/pages/api/websites/[id]/index.ts b/pages/api/websites/[id]/index.ts index 1d7e4ac3..3d053d0e 100644 --- a/pages/api/websites/[id]/index.ts +++ b/pages/api/websites/[id]/index.ts @@ -3,7 +3,7 @@ import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics'; import { Website, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; -import { deleteWebsite, getWebsite, updateWebsite } from 'queries'; +import { deleteWebsite, getWebsiteById, updateWebsite } from 'queries'; import { SHARE_ID_REGEX } from 'lib/constants'; export interface WebsiteRequestQuery { @@ -30,7 +30,7 @@ export default async ( return unauthorized(res); } - const website = await getWebsite({ id: websiteId }); + const website = await getWebsiteById(websiteId); return ok(res, website); } diff --git a/queries/admin/report.ts b/queries/admin/report.ts index 6b557a7a..ee7a0592 100644 --- a/queries/admin/report.ts +++ b/queries/admin/report.ts @@ -13,19 +13,29 @@ export async function getReportById(reportId: string): Promise { }); } -export async function getReports(where: Prisma.ReportWhereInput): Promise { +export async function getUserReports(userId: string): Promise { return prisma.client.report.findMany({ - where, + where: { + userId, + }, + }); +} + +export async function getWebsiteReports(websiteId: string): Promise { + return prisma.client.report.findMany({ + where: { + websiteId, + }, }); } export async function updateReport( + reportId: string, data: Prisma.ReportUpdateInput, - where: Prisma.ReportWhereUniqueInput, ): Promise { - return prisma.client.report.update({ data, where }); + return prisma.client.report.update({ where: { id: reportId }, data }); } -export async function deleteReport(where: Prisma.ReportWhereUniqueInput): Promise { - return prisma.client.report.delete({ where }); +export async function deleteReport(reportId: string): Promise { + return prisma.client.report.delete({ where: { id: reportId } }); } diff --git a/queries/admin/team.ts b/queries/admin/team.ts index 5c3b372f..a8b3385c 100644 --- a/queries/admin/team.ts +++ b/queries/admin/team.ts @@ -3,15 +3,29 @@ import prisma from 'lib/prisma'; import { ROLES } from 'lib/constants'; import { uuid } from 'lib/crypto'; -export async function getTeam(where: Prisma.TeamWhereInput): Promise { +export interface GetTeamOptions { + includeTeamUser?: boolean; +} + +async function getTeam(where: Prisma.TeamWhereInput, options: GetTeamOptions = {}): Promise { + const { includeTeamUser = false } = options; + return prisma.client.team.findFirst({ where, include: { - teamUser: true, + teamUser: includeTeamUser, }, }); } +export function getTeamById(teamId: string, options: GetTeamOptions = {}) { + return getTeam({ id: teamId }, options); +} + +export function getTeamByAccessCode(accessCode: string, options: GetTeamOptions = {}) { + return getTeam({ accessCode }, options); +} + export async function getTeams(where: Prisma.TeamWhereInput): Promise { return prisma.client.team.findMany({ where, @@ -36,16 +50,15 @@ export async function createTeam(data: Prisma.TeamCreateInput, userId: string): ]); } -export async function updateTeam( - data: Prisma.TeamUpdateInput, - where: Prisma.TeamWhereUniqueInput, -): Promise { +export async function updateTeam(teamId: string, data: Prisma.TeamUpdateInput): Promise { return prisma.client.team.update({ + where: { + id: teamId, + }, data: { ...data, updatedAt: new Date(), }, - where, }); } diff --git a/queries/admin/teamUser.ts b/queries/admin/teamUser.ts index b1c295be..c5b27a02 100644 --- a/queries/admin/teamUser.ts +++ b/queries/admin/teamUser.ts @@ -53,12 +53,14 @@ export async function createTeamUser( } export async function updateTeamUser( + teamUserId: string, data: Prisma.TeamUserUpdateInput, - where: Prisma.TeamUserWhereUniqueInput, ): Promise { return prisma.client.teamUser.update({ + where: { + id: teamUserId, + }, data, - where, }); } diff --git a/queries/admin/teamWebsite.ts b/queries/admin/teamWebsite.ts index 0aedc3c7..169526dd 100644 --- a/queries/admin/teamWebsite.ts +++ b/queries/admin/teamWebsite.ts @@ -22,7 +22,7 @@ export async function getTeamWebsite( }); } -export async function getTeamWebsiteByTeamMemberId( +export async function findTeamWebsiteByUserId( websiteId: string, userId: string, ): Promise { diff --git a/queries/admin/user.ts b/queries/admin/user.ts index cab8a562..f60c4801 100644 --- a/queries/admin/user.ts +++ b/queries/admin/user.ts @@ -5,9 +5,14 @@ import { ROLES } from 'lib/constants'; import prisma from 'lib/prisma'; import { Website, User, Role } from 'lib/types'; -export async function getUser( +export interface GetUserOptions { + includePassword?: boolean; + showDeleted?: boolean; +} + +async function getUser( where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput, - options: { includePassword?: boolean; showDeleted?: boolean } = {}, + options: GetUserOptions = {}, ): Promise { const { includePassword = false, showDeleted = false } = options; @@ -23,6 +28,14 @@ export async function getUser( }); } +export async function getUserById(userId: string, options: GetUserOptions = {}) { + return getUser({ id: userId }, options); +} + +export async function getUserByUsername(username: string, options: GetUserOptions = {}) { + return getUser({ username }, options); +} + export async function getUsers(): Promise { return prisma.client.user.findMany({ take: 100, diff --git a/queries/admin/website.ts b/queries/admin/website.ts index 8714d8c9..35f32bac 100644 --- a/queries/admin/website.ts +++ b/queries/admin/website.ts @@ -2,12 +2,20 @@ import { Prisma, Website } from '@prisma/client'; import cache from 'lib/cache'; import prisma from 'lib/prisma'; -export async function getWebsite(where: Prisma.WebsiteWhereUniqueInput): Promise { +async function getWebsite(where: Prisma.WebsiteWhereUniqueInput): Promise { return prisma.client.website.findUnique({ where, }); } +export async function getWebsiteById(id: string) { + return getWebsite({ id }); +} + +export async function getWebsiteByShareId(shareId: string) { + return getWebsite({ shareId }); +} + export async function getWebsites(): Promise { return prisma.client.website.findMany({ orderBy: { diff --git a/queries/analytics/eventData/saveEventData.ts b/queries/analytics/eventData/saveEventData.ts index 97b1c882..0f1ddb37 100644 --- a/queries/analytics/eventData/saveEventData.ts +++ b/queries/analytics/eventData/saveEventData.ts @@ -2,7 +2,7 @@ import { Prisma } from '@prisma/client'; import { DATA_TYPE } from 'lib/constants'; import { uuid } from 'lib/crypto'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; -import { flattenJSON } from 'lib/dynamicData'; +import { flattenJSON } from 'lib/data'; import kafka from 'lib/kafka'; import prisma from 'lib/prisma'; import { DynamicData } from 'lib/types'; diff --git a/queries/analytics/events/getEventMetrics.ts b/queries/analytics/events/getEventMetrics.ts index 37044d1b..11a5c690 100644 --- a/queries/analytics/events/getEventMetrics.ts +++ b/queries/analytics/events/getEventMetrics.ts @@ -2,24 +2,23 @@ import prisma from 'lib/prisma'; import clickhouse from 'lib/clickhouse'; import { runQuery, CLICKHOUSE, PRISMA } from 'lib/db'; import { WebsiteEventMetric } from 'lib/types'; -import { DEFAULT_RESET_DATE, EVENT_TYPE } from 'lib/constants'; +import { EVENT_TYPE } from 'lib/constants'; import { loadWebsite } from 'lib/load'; import { maxDate } from 'lib/date'; +export interface GetEventMetricsCriteria { + startDate: Date; + endDate: Date; + timezone: string; + unit: string; + filters: { + url: string; + eventName: string; + }; +} + export async function getEventMetrics( - ...args: [ - websiteId: string, - data: { - startDate: Date; - endDate: Date; - timezone: string; - unit: string; - filters: { - url: string; - eventName: string; - }; - }, - ] + ...args: [websiteId: string, criteria: GetEventMetricsCriteria] ): Promise { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -27,25 +26,8 @@ export async function getEventMetrics( }); } -async function relationalQuery( - websiteId: string, - { - startDate, - endDate, - timezone = 'utc', - unit = 'day', - filters, - }: { - startDate: Date; - endDate: Date; - timezone: string; - unit: string; - filters: { - url: string; - eventName: string; - }; - }, -) { +async function relationalQuery(websiteId: string, criteria: GetEventMetricsCriteria) { + const { startDate, endDate, timezone = 'utc', unit = 'day', filters } = criteria; const { rawQuery, getDateQuery, getFilterQuery } = prisma; const website = await loadWebsite(websiteId); const filterQuery = getFilterQuery(filters); @@ -74,25 +56,8 @@ async function relationalQuery( ); } -async function clickhouseQuery( - websiteId: string, - { - startDate, - endDate, - timezone = 'utc', - unit = 'day', - filters, - }: { - startDate: Date; - endDate: Date; - timezone: string; - unit: string; - filters: { - url: string; - eventName: string; - }; - }, -) { +async function clickhouseQuery(websiteId: string, criteria: GetEventMetricsCriteria) { + const { startDate, endDate, timezone = 'utc', unit = 'day', filters } = criteria; const { rawQuery, getDateQuery, getFilterQuery } = clickhouse; const website = await loadWebsite(websiteId); const filterQuery = getFilterQuery(filters); diff --git a/queries/analytics/sessions/getSession.ts b/queries/analytics/sessions/getSession.ts index 2fd8d18f..256ada4c 100644 --- a/queries/analytics/sessions/getSession.ts +++ b/queries/analytics/sessions/getSession.ts @@ -1,8 +1,9 @@ -import { Prisma } from '@prisma/client'; import prisma from 'lib/prisma'; -export async function getSession(where: Prisma.SessionWhereUniqueInput) { +export async function getSession(id: string) { return prisma.client.session.findUnique({ - where, + where: { + id, + }, }); } diff --git a/queries/analytics/sessions/saveSessionData.ts b/queries/analytics/sessions/saveSessionData.ts index 192053f1..ef32bcfb 100644 --- a/queries/analytics/sessions/saveSessionData.ts +++ b/queries/analytics/sessions/saveSessionData.ts @@ -1,6 +1,6 @@ import { DATA_TYPE } from 'lib/constants'; import { uuid } from 'lib/crypto'; -import { flattenJSON } from 'lib/dynamicData'; +import { flattenJSON } from 'lib/data'; import prisma from 'lib/prisma'; import { DynamicData } from 'lib/types'; diff --git a/queries/index.js b/queries/index.js index f86551c4..f509e039 100644 --- a/queries/index.js +++ b/queries/index.js @@ -1,7 +1,8 @@ +export * from './admin/report'; export * from './admin/team'; export * from './admin/teamUser'; +export * from './admin/teamWebsite'; export * from './admin/user'; -export * from './admin/report'; export * from './admin/website'; export * from './analytics/events/getEventMetrics'; export * from './analytics/events/getEventUsage';