mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-14 21:10:34 +01:00
Merge branch 'dev' into analytics
This commit is contained in:
commit
5452ad437f
29
components/common/ConfirmDeleteForm.js
Normal file
29
components/common/ConfirmDeleteForm.js
Normal file
@ -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 (
|
||||
<Form>
|
||||
<p>
|
||||
<FormattedMessage {...messages.confirmDelete} values={{ target: <b>{name}</b> }} />
|
||||
</p>
|
||||
<FormButtons flex>
|
||||
<LoadingButton loading={loading} onClick={handleConfirm} variant="danger">
|
||||
{formatMessage(labels.delete)}
|
||||
</LoadingButton>
|
||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConfirmDeleteForm;
|
12
components/common/LinkButton.js
Normal file
12
components/common/LinkButton.js
Normal file
@ -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 (
|
||||
<Link className={styles.button} href={href}>
|
||||
<Icon>{icon || <Icons.ArrowRight />}</Icon>
|
||||
<Text>{children}</Text>
|
||||
</Link>
|
||||
);
|
||||
}
|
28
components/common/LinkButton.module.css
Normal file
28
components/common/LinkButton.module.css
Normal file
@ -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);
|
||||
}
|
@ -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 (
|
||||
<SettingsTable columns={columns} data={data}>
|
||||
{row => {
|
||||
const { id } = row;
|
||||
const handleConfirm = () => {
|
||||
onDelete(report.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href={`/reports/${id}`}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}}
|
||||
</SettingsTable>
|
||||
return (
|
||||
<>
|
||||
<SettingsTable columns={columns} data={data}>
|
||||
{row => {
|
||||
const { id } = row;
|
||||
|
||||
return (
|
||||
<Flexbox gap={10}>
|
||||
<LinkButton href={`/reports/${id}`}>{formatMessage(labels.view)}</LinkButton>
|
||||
<Button onClick={() => setReport(row)}>
|
||||
<Icon>
|
||||
<Icons.Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
</Button>
|
||||
</Flexbox>
|
||||
);
|
||||
}}
|
||||
</SettingsTable>
|
||||
{report && (
|
||||
<Modal>
|
||||
<ConfirmDeleteForm
|
||||
name={report.name}
|
||||
onConfirm={handleConfirm}
|
||||
onClose={() => setReport(null)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<Page loading={isLoading} error={error}>
|
||||
@ -22,7 +26,7 @@ export function WebsiteReportsPage({ websiteId }) {
|
||||
</Button>
|
||||
</Link>
|
||||
</Flexbox>
|
||||
<ReportsTable websiteId={websiteId} data={reports} />
|
||||
<ReportsTable data={reports} onDelete={handleDelete} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
125
lib/auth.ts
125
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);
|
||||
|
@ -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<Website> {
|
||||
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<User> {
|
||||
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) {
|
||||
|
@ -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<Website> {
|
||||
@ -8,7 +8,7 @@ export async function loadWebsite(websiteId: string): Promise<Website> {
|
||||
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<Session> {
|
||||
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<User> {
|
||||
if (cache.enabled) {
|
||||
user = await cache.fetchUser(userId);
|
||||
} else {
|
||||
user = await getUser({ id: userId });
|
||||
user = await getUserById(userId);
|
||||
}
|
||||
|
||||
if (!user || user.deletedAt) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 };
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -13,19 +13,29 @@ export async function getReportById(reportId: string): Promise<Report> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getReports(where: Prisma.ReportWhereInput): Promise<Report[]> {
|
||||
export async function getUserReports(userId: string): Promise<Report[]> {
|
||||
return prisma.client.report.findMany({
|
||||
where,
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getWebsiteReports(websiteId: string): Promise<Report[]> {
|
||||
return prisma.client.report.findMany({
|
||||
where: {
|
||||
websiteId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateReport(
|
||||
reportId: string,
|
||||
data: Prisma.ReportUpdateInput,
|
||||
where: Prisma.ReportWhereUniqueInput,
|
||||
): Promise<Report> {
|
||||
return prisma.client.report.update({ data, where });
|
||||
return prisma.client.report.update({ where: { id: reportId }, data });
|
||||
}
|
||||
|
||||
export async function deleteReport(where: Prisma.ReportWhereUniqueInput): Promise<Report> {
|
||||
return prisma.client.report.delete({ where });
|
||||
export async function deleteReport(reportId: string): Promise<Report> {
|
||||
return prisma.client.report.delete({ where: { id: reportId } });
|
||||
}
|
||||
|
@ -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<Team> {
|
||||
export interface GetTeamOptions {
|
||||
includeTeamUser?: boolean;
|
||||
}
|
||||
|
||||
async function getTeam(where: Prisma.TeamWhereInput, options: GetTeamOptions = {}): Promise<Team> {
|
||||
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<Team[]> {
|
||||
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<Team> {
|
||||
export async function updateTeam(teamId: string, data: Prisma.TeamUpdateInput): Promise<Team> {
|
||||
return prisma.client.team.update({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
data: {
|
||||
...data,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
where,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -53,12 +53,14 @@ export async function createTeamUser(
|
||||
}
|
||||
|
||||
export async function updateTeamUser(
|
||||
teamUserId: string,
|
||||
data: Prisma.TeamUserUpdateInput,
|
||||
where: Prisma.TeamUserWhereUniqueInput,
|
||||
): Promise<TeamUser> {
|
||||
return prisma.client.teamUser.update({
|
||||
where: {
|
||||
id: teamUserId,
|
||||
},
|
||||
data,
|
||||
where,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ export async function getTeamWebsite(
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTeamWebsiteByTeamMemberId(
|
||||
export async function findTeamWebsiteByUserId(
|
||||
websiteId: string,
|
||||
userId: string,
|
||||
): Promise<TeamWebsite> {
|
||||
|
@ -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<User> {
|
||||
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<User[]> {
|
||||
return prisma.client.user.findMany({
|
||||
take: 100,
|
||||
|
@ -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<Website> {
|
||||
async function getWebsite(where: Prisma.WebsiteWhereUniqueInput): Promise<Website> {
|
||||
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<Website[]> {
|
||||
return prisma.client.website.findMany({
|
||||
orderBy: {
|
||||
|
@ -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';
|
||||
|
@ -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<WebsiteEventMetric[]> {
|
||||
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);
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user