mirror of
https://github.com/kremalicious/umami.git
synced 2024-06-30 13:41:50 +02:00
Refactored queries.
This commit is contained in:
parent
18e36aa7b3
commit
b16f5cc067
|
@ -6,31 +6,30 @@ import Icons from 'components/icons';
|
||||||
import ThemeButton from 'components/input/ThemeButton';
|
import ThemeButton from 'components/input/ThemeButton';
|
||||||
import LanguageButton from 'components/input/LanguageButton';
|
import LanguageButton from 'components/input/LanguageButton';
|
||||||
import ProfileButton from 'components/input/ProfileButton';
|
import ProfileButton from 'components/input/ProfileButton';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages, useNavigation } from 'components/hooks';
|
||||||
import HamburgerButton from 'components/common/HamburgerButton';
|
import HamburgerButton from 'components/common/HamburgerButton';
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import styles from './NavBar.module.css';
|
import styles from './NavBar.module.css';
|
||||||
|
|
||||||
export function NavBar() {
|
export function NavBar() {
|
||||||
const pathname = usePathname();
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const cloudMode = Boolean(process.env.cloudMode);
|
const cloudMode = Boolean(process.env.cloudMode);
|
||||||
|
const { pathname, renderTeamUrl } = useNavigation();
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ label: formatMessage(labels.dashboard), url: '/dashboard' },
|
{ label: formatMessage(labels.dashboard), url: renderTeamUrl('/dashboard') },
|
||||||
{ label: formatMessage(labels.websites), url: '/websites' },
|
{ label: formatMessage(labels.websites), url: renderTeamUrl('/websites') },
|
||||||
{ label: formatMessage(labels.reports), url: '/reports' },
|
{ label: formatMessage(labels.reports), url: renderTeamUrl('/reports') },
|
||||||
{ label: formatMessage(labels.settings), url: '/settings' },
|
{ label: formatMessage(labels.settings), url: renderTeamUrl('/settings') },
|
||||||
].filter(n => n);
|
].filter(n => n);
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.dashboard),
|
label: formatMessage(labels.dashboard),
|
||||||
url: '/dashboard',
|
url: renderTeamUrl('/dashboard'),
|
||||||
},
|
},
|
||||||
!cloudMode && {
|
!cloudMode && {
|
||||||
label: formatMessage(labels.settings),
|
label: formatMessage(labels.settings),
|
||||||
url: '/settings',
|
url: renderTeamUrl('/settings'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: formatMessage(labels.websites),
|
label: formatMessage(labels.websites),
|
||||||
|
|
|
@ -5,14 +5,14 @@ async function getEnabled() {
|
||||||
return !!process.env.ENABLE_TEST_CONSOLE;
|
return !!process.env.ENABLE_TEST_CONSOLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ({ params: { id } }) {
|
export default async function ({ params: { websiteId } }) {
|
||||||
const enabled = await getEnabled();
|
const enabled = await getEnabled();
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <TestConsole websiteId={id?.[0]} />;
|
return <TestConsole websiteId={websiteId?.[0]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
|
@ -3,8 +3,14 @@ import { useReports } from 'components/hooks';
|
||||||
import ReportsTable from './ReportsTable';
|
import ReportsTable from './ReportsTable';
|
||||||
import DataTable from 'components/common/DataTable';
|
import DataTable from 'components/common/DataTable';
|
||||||
|
|
||||||
export default function ReportsDataTable({ websiteId }: { websiteId?: string }) {
|
export default function ReportsDataTable({
|
||||||
const queryResult = useReports(websiteId);
|
websiteId,
|
||||||
|
teamId,
|
||||||
|
}: {
|
||||||
|
websiteId?: string;
|
||||||
|
teamId?: string;
|
||||||
|
}) {
|
||||||
|
const queryResult = useReports({ websiteId, teamId });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable queryResult={queryResult}>
|
<DataTable queryResult={queryResult}>
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
'use client';
|
'use client';
|
||||||
import PageHeader from 'components/layout/PageHeader';
|
import PageHeader from 'components/layout/PageHeader';
|
||||||
import { Button, Icon, Icons, Text } from 'react-basics';
|
import { Icon, Icons, Text } from 'react-basics';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages, useNavigation } from 'components/hooks';
|
||||||
import { useRouter } from 'next/navigation';
|
import LinkButton from 'components/common/LinkButton';
|
||||||
|
|
||||||
export function ReportsHeader() {
|
export function ReportsHeader() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const router = useRouter();
|
const { renderTeamUrl } = useNavigation();
|
||||||
|
|
||||||
const handleClick = () => router.push('/reports/create');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader title={formatMessage(labels.reports)}>
|
<PageHeader title={formatMessage(labels.reports)}>
|
||||||
<Button variant="primary" onClick={handleClick}>
|
<LinkButton href={renderTeamUrl('/reports/create')} variant="primary">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>{formatMessage(labels.createReport)}</Text>
|
<Text>{formatMessage(labels.createReport)}</Text>
|
||||||
</Button>
|
</LinkButton>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Funnel from 'assets/funnel.svg';
|
||||||
import Lightbulb from 'assets/lightbulb.svg';
|
import Lightbulb from 'assets/lightbulb.svg';
|
||||||
import Magnet from 'assets/magnet.svg';
|
import Magnet from 'assets/magnet.svg';
|
||||||
import styles from './ReportTemplates.module.css';
|
import styles from './ReportTemplates.module.css';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages, useNavigation } from 'components/hooks';
|
||||||
|
|
||||||
function ReportItem({ title, description, url, icon }) {
|
function ReportItem({ title, description, url, icon }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
@ -32,26 +32,27 @@ function ReportItem({ title, description, url, icon }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReportTemplates({ showHeader = true }) {
|
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { renderTeamUrl } = useNavigation();
|
||||||
|
|
||||||
const reports = [
|
const reports = [
|
||||||
{
|
{
|
||||||
title: formatMessage(labels.insights),
|
title: formatMessage(labels.insights),
|
||||||
description: formatMessage(labels.insightsDescription),
|
description: formatMessage(labels.insightsDescription),
|
||||||
url: '/reports/insights',
|
url: renderTeamUrl('/reports/insights'),
|
||||||
icon: <Lightbulb />,
|
icon: <Lightbulb />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: formatMessage(labels.funnel),
|
title: formatMessage(labels.funnel),
|
||||||
description: formatMessage(labels.funnelDescription),
|
description: formatMessage(labels.funnelDescription),
|
||||||
url: '/reports/funnel',
|
url: renderTeamUrl('/reports/funnel'),
|
||||||
icon: <Funnel />,
|
icon: <Funnel />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: formatMessage(labels.retention),
|
title: formatMessage(labels.retention),
|
||||||
description: formatMessage(labels.retentionDescription),
|
description: formatMessage(labels.retentionDescription),
|
||||||
url: '/reports/retention',
|
url: renderTeamUrl('/reports/retention'),
|
||||||
icon: <Magnet />,
|
icon: <Magnet />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
import RetentionReport from './RetentionReport';
|
import RetentionReport from './RetentionReport';
|
||||||
|
|
||||||
export default function RetentionReportPage() {
|
export default function RetentionReportPage() {
|
||||||
return <RetentionReport reportId={null} />;
|
return <RetentionReport reportId={null} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Create Report | umami',
|
title: 'Create Report | umami',
|
||||||
};
|
};
|
|
@ -28,7 +28,7 @@ export function LanguageSetting() {
|
||||||
items={options}
|
items={options}
|
||||||
value={locale}
|
value={locale}
|
||||||
renderValue={renderValue}
|
renderValue={renderValue}
|
||||||
onChange={saveLocale}
|
onChange={val => saveLocale(val as string)}
|
||||||
allowSearch={true}
|
allowSearch={true}
|
||||||
onSearch={setSearch}
|
onSearch={setSearch}
|
||||||
menuProps={{ className: styles.menu }}
|
menuProps={{ className: styles.menu }}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Button,
|
Button,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
} from 'react-basics';
|
} from 'react-basics';
|
||||||
import { setValue } from 'store/cache';
|
import { touch } from 'store/cache';
|
||||||
import { useApi, useMessages } from 'components/hooks';
|
import { useApi, useMessages } from 'components/hooks';
|
||||||
|
|
||||||
export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
||||||
|
@ -17,10 +17,10 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose:
|
||||||
mutationFn: (data: any) => post('/teams', data),
|
mutationFn: (data: any) => post('/teams', data),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async (data: any) => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
setValue('teams', Date.now());
|
touch('teams');
|
||||||
onSave?.();
|
onSave?.();
|
||||||
onClose?.();
|
onClose?.();
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
} from 'react-basics';
|
} from 'react-basics';
|
||||||
import { useApi, useMessages } from 'components/hooks';
|
import { useApi, useMessages } from 'components/hooks';
|
||||||
import { setValue } from 'store/cache';
|
import { touch } from 'store/cache';
|
||||||
|
|
||||||
export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
||||||
const { formatMessage, labels, getMessage } = useMessages();
|
const { formatMessage, labels, getMessage } = useMessages();
|
||||||
|
@ -20,7 +20,7 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose:
|
||||||
const handleSubmit = async (data: any) => {
|
const handleSubmit = async (data: any) => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
setValue('teams:members', Date.now());
|
touch('teams:members');
|
||||||
onSave?.();
|
onSave?.();
|
||||||
onClose?.();
|
onClose?.();
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,11 +18,11 @@ const generateId = () => getRandomChars(16);
|
||||||
export function TeamEditForm({
|
export function TeamEditForm({
|
||||||
teamId,
|
teamId,
|
||||||
data,
|
data,
|
||||||
readOnly,
|
allowEdit,
|
||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
data?: { name: string; accessCode: string };
|
data?: { name: string; accessCode: string };
|
||||||
readOnly?: boolean;
|
allowEdit?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
|
@ -57,22 +57,24 @@ export function TeamEditForm({
|
||||||
<TextField value={teamId} readOnly allowCopy />
|
<TextField value={teamId} readOnly allowCopy />
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow label={formatMessage(labels.name)}>
|
<FormRow label={formatMessage(labels.name)}>
|
||||||
{!readOnly && (
|
{allowEdit && (
|
||||||
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||||
<TextField />
|
<TextField />
|
||||||
</FormInput>
|
</FormInput>
|
||||||
)}
|
)}
|
||||||
{readOnly && data.name}
|
{!allowEdit && data.name}
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow label={formatMessage(labels.accessCode)}>
|
{allowEdit && (
|
||||||
<Flexbox gap={10}>
|
<FormRow label={formatMessage(labels.accessCode)}>
|
||||||
<TextField value={accessCode} readOnly allowCopy />
|
<Flexbox gap={10}>
|
||||||
{!readOnly && (
|
<TextField value={accessCode} readOnly allowCopy />
|
||||||
<Button onClick={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
|
{allowEdit && (
|
||||||
)}
|
<Button onClick={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
|
||||||
</Flexbox>
|
)}
|
||||||
</FormRow>
|
</Flexbox>
|
||||||
{!readOnly && (
|
</FormRow>
|
||||||
|
)}
|
||||||
|
{allowEdit && (
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
|
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useApi, useMessages } from 'components/hooks';
|
import { useApi, useMessages } from 'components/hooks';
|
||||||
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
||||||
import { setValue } from 'store/cache';
|
import { touch } from 'store/cache';
|
||||||
|
|
||||||
export function TeamMemberRemoveButton({
|
export function TeamMemberRemoveButton({
|
||||||
teamId,
|
teamId,
|
||||||
|
@ -22,7 +22,7 @@ export function TeamMemberRemoveButton({
|
||||||
const handleRemoveTeamMember = () => {
|
const handleRemoveTeamMember = () => {
|
||||||
mutate(null, {
|
mutate(null, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setValue('team:members', Date.now());
|
touch('team:members');
|
||||||
onSave?.();
|
onSave?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,24 +1,13 @@
|
||||||
import { useApi, useFilterQuery } from 'components/hooks';
|
|
||||||
import DataTable from 'components/common/DataTable';
|
import DataTable from 'components/common/DataTable';
|
||||||
import useCache from 'store/cache';
|
|
||||||
import TeamMembersTable from './TeamMembersTable';
|
import TeamMembersTable from './TeamMembersTable';
|
||||||
|
import useTeamMembers from 'components/hooks/queries/useTeamMembers';
|
||||||
|
|
||||||
export function TeamMembers({ teamId, readOnly }: { teamId: string; readOnly: boolean }) {
|
export function TeamMembers({ teamId, allowEdit }: { teamId: string; allowEdit: boolean }) {
|
||||||
const { get } = useApi();
|
const queryResult = useTeamMembers(teamId);
|
||||||
const modified = useCache(state => state?.['team:members']);
|
|
||||||
const queryResult = useFilterQuery({
|
|
||||||
queryKey: ['team:members', { teamId, modified }],
|
|
||||||
queryFn: params => {
|
|
||||||
return get(`/teams/${teamId}/users`, {
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
enabled: !!teamId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable queryResult={queryResult}>
|
<DataTable queryResult={queryResult}>
|
||||||
{({ data }) => <TeamMembersTable data={data} teamId={teamId} readOnly={readOnly} />}
|
{({ data }) => <TeamMembersTable data={data} teamId={teamId} allowEdit={allowEdit} />}
|
||||||
</DataTable>
|
</DataTable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ import TeamMemberRemoveButton from './TeamMemberRemoveButton';
|
||||||
export function TeamMembersTable({
|
export function TeamMembersTable({
|
||||||
data = [],
|
data = [],
|
||||||
teamId,
|
teamId,
|
||||||
readOnly,
|
allowEdit,
|
||||||
}: {
|
}: {
|
||||||
data: any[];
|
data: any[];
|
||||||
teamId: string;
|
teamId: string;
|
||||||
readOnly: boolean;
|
allowEdit: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { user } = useLogin();
|
const { user } = useLogin();
|
||||||
|
@ -23,16 +23,20 @@ export function TeamMembersTable({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridTable data={data} cardMode={['xs', 'sm', 'md'].includes(breakpoint)}>
|
<GridTable data={data} cardMode={['xs', 'sm', 'md'].includes(breakpoint)}>
|
||||||
<GridColumn name="username" label={formatMessage(labels.username)} />
|
<GridColumn name="username" label={formatMessage(labels.username)}>
|
||||||
|
{row => row?.user?.username}
|
||||||
|
</GridColumn>
|
||||||
<GridColumn name="role" label={formatMessage(labels.role)}>
|
<GridColumn name="role" label={formatMessage(labels.role)}>
|
||||||
{row => roles[row?.teamUser?.[0]?.role]}
|
{row => roles[row?.role]}
|
||||||
</GridColumn>
|
</GridColumn>
|
||||||
<GridColumn name="action" label=" " alignment="end">
|
<GridColumn name="action" label=" " alignment="end">
|
||||||
{row => {
|
{row => {
|
||||||
return (
|
return (
|
||||||
!readOnly &&
|
allowEdit &&
|
||||||
row?.teamUser?.[0]?.role !== ROLES.teamOwner &&
|
row?.role !== ROLES.teamOwner &&
|
||||||
user?.id !== row?.id && <TeamMemberRemoveButton teamId={teamId} userId={row.id} />
|
user?.id !== row?.id && (
|
||||||
|
<TeamMemberRemoveButton teamId={teamId} userId={row?.user?.id} />
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</GridColumn>
|
</GridColumn>
|
||||||
|
|
|
@ -47,9 +47,9 @@ export function TeamSettings({ teamId }: { teamId: string }) {
|
||||||
<Item key="websites">{formatMessage(labels.websites)}</Item>
|
<Item key="websites">{formatMessage(labels.websites)}</Item>
|
||||||
<Item key="data">{formatMessage(labels.data)}</Item>
|
<Item key="data">{formatMessage(labels.data)}</Item>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{tab === 'details' && <TeamEditForm teamId={teamId} data={team} readOnly={!canEdit} />}
|
{tab === 'details' && <TeamEditForm teamId={teamId} data={team} allowEdit={canEdit} />}
|
||||||
{tab === 'members' && <TeamMembers teamId={teamId} readOnly={!canEdit} />}
|
{tab === 'members' && <TeamMembers teamId={teamId} allowEdit={canEdit} />}
|
||||||
{tab === 'websites' && <TeamWebsites teamId={teamId} readOnly={!canEdit} />}
|
{tab === 'websites' && <TeamWebsites teamId={teamId} allowEdit={canEdit} />}
|
||||||
{canEdit && tab === 'data' && <TeamData teamId={teamId} />}
|
{canEdit && tab === 'data' && <TeamData teamId={teamId} />}
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
</TeamsContext.Provider>
|
</TeamsContext.Provider>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable';
|
import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable';
|
||||||
|
|
||||||
export function TeamWebsites({ teamId }: { teamId: string; readOnly: boolean }) {
|
export function TeamWebsites({ teamId, allowEdit }: { teamId: string; allowEdit: boolean }) {
|
||||||
return <WebsitesDataTable teamId={teamId} />;
|
return <WebsitesDataTable teamId={teamId} allowEdit={allowEdit} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TeamWebsites;
|
export default TeamWebsites;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||||
import WebsiteAddForm from './WebsiteAddForm';
|
import WebsiteAddForm from './WebsiteAddForm';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages } from 'components/hooks';
|
||||||
import { setValue } from 'store/cache';
|
import { touch } from 'store/cache';
|
||||||
|
|
||||||
export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) {
|
export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
|
@ -9,7 +9,7 @@ export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?:
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||||
setValue('websites', Date.now());
|
touch('websites');
|
||||||
onSave?.();
|
onSave?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import WebsitesHeader from './WebsitesHeader';
|
||||||
|
|
||||||
export default function Websites() {
|
export default function Websites() {
|
||||||
const { user } = useLogin();
|
const { user } = useLogin();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WebsitesHeader showActions={user.role !== 'view-only'} />
|
<WebsitesHeader showActions={user.role !== 'view-only'} />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button, Text, Icon, Icons, GridTable, GridColumn, useBreakpoint } from 'react-basics';
|
import { Button, Text, Icon, Icons, GridTable, GridColumn, useBreakpoint } from 'react-basics';
|
||||||
import { useMessages, useLogin } from 'components/hooks';
|
import { useMessages, useLogin, useNavigation } from 'components/hooks';
|
||||||
|
|
||||||
export interface WebsitesTableProps {
|
export interface WebsitesTableProps {
|
||||||
data: any[];
|
data: any[];
|
||||||
|
@ -23,6 +23,7 @@ export function WebsitesTable({
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { user } = useLogin();
|
const { user } = useLogin();
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
|
const { renderTeamUrl } = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridTable data={data} cardMode={['xs', 'sm', 'md'].includes(breakpoint)}>
|
<GridTable data={data} cardMode={['xs', 'sm', 'md'].includes(breakpoint)}>
|
||||||
|
@ -46,7 +47,7 @@ export function WebsitesTable({
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{allowView && (
|
{allowView && (
|
||||||
<Link href={teamId ? `/teams/${teamId}/websites/${id}` : `/websites/${id}`}>
|
<Link href={renderTeamUrl(`/websites/${id}`)}>
|
||||||
<Button>
|
<Button>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.External />
|
<Icons.External />
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import WebsiteSettings from '../WebsiteSettings';
|
import WebsiteSettings from '../WebsiteSettings';
|
||||||
|
|
||||||
export default async function WebsiteSettingsPage({ params: { id } }) {
|
export default async function WebsiteSettingsPage({ params: { websiteId } }) {
|
||||||
if (process.env.cloudMode) {
|
if (process.env.cloudMode || !websiteId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <WebsiteSettings websiteId={id} />;
|
return <WebsiteSettings websiteId={websiteId} />;
|
||||||
}
|
}
|
||||||
|
|
27
src/app/(main)/teams/[teamId]/reports/TeamReports.tsx
Normal file
27
src/app/(main)/teams/[teamId]/reports/TeamReports.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
'use client';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { Button, Flexbox, Icon, Icons, Text } from 'react-basics';
|
||||||
|
import { useMessages } from 'components/hooks';
|
||||||
|
import ReportsDataTable from 'app/(main)/reports/ReportsDataTable';
|
||||||
|
|
||||||
|
export function TeamReports({ websiteId }) {
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flexbox alignItems="center" justifyContent="end">
|
||||||
|
<Link href={`/reports/create`}>
|
||||||
|
<Button variant="primary">
|
||||||
|
<Icon>
|
||||||
|
<Icons.Plus />
|
||||||
|
</Icon>
|
||||||
|
<Text>{formatMessage(labels.createReport)}</Text>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Flexbox>
|
||||||
|
<ReportsDataTable websiteId={websiteId} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TeamReports;
|
10
src/app/(main)/teams/[teamId]/reports/create/page.tsx
Normal file
10
src/app/(main)/teams/[teamId]/reports/create/page.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import ReportTemplates from 'app/(main)/reports/create/ReportTemplates';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return <ReportTemplates />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Create Report | umami',
|
||||||
|
};
|
15
src/app/(main)/teams/[teamId]/reports/page.tsx
Normal file
15
src/app/(main)/teams/[teamId]/reports/page.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import ReportsHeader from 'app/(main)/reports/ReportsHeader';
|
||||||
|
import ReportsDataTable from 'app/(main)/reports/ReportsDataTable';
|
||||||
|
|
||||||
|
export default function ({ params: { teamId } }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ReportsHeader />
|
||||||
|
<ReportsDataTable teamId={teamId} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Reports | umami',
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import WebsiteDetails from '../../../../websites/[websiteId]/WebsiteDetails';
|
import WebsiteDetails from 'app/(main)/websites/[websiteId]/WebsiteDetails';
|
||||||
|
|
||||||
export default function TeamWebsitePage({ params: { websiteId } }) {
|
export default function TeamWebsitePage({ params: { websiteId } }) {
|
||||||
return <WebsiteDetails websiteId={websiteId} />;
|
return <WebsiteDetails websiteId={websiteId} />;
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default function WebsiteExpandedView({
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const {
|
const {
|
||||||
router,
|
router,
|
||||||
makeUrl,
|
renderUrl,
|
||||||
pathname,
|
pathname,
|
||||||
query: { view },
|
query: { view },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
|
@ -52,69 +52,69 @@ export default function WebsiteExpandedView({
|
||||||
{
|
{
|
||||||
key: 'url',
|
key: 'url',
|
||||||
label: formatMessage(labels.pages),
|
label: formatMessage(labels.pages),
|
||||||
url: makeUrl({ view: 'url' }),
|
url: renderUrl({ view: 'url' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'referrer',
|
key: 'referrer',
|
||||||
label: formatMessage(labels.referrers),
|
label: formatMessage(labels.referrers),
|
||||||
url: makeUrl({ view: 'referrer' }),
|
url: renderUrl({ view: 'referrer' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'browser',
|
key: 'browser',
|
||||||
label: formatMessage(labels.browsers),
|
label: formatMessage(labels.browsers),
|
||||||
url: makeUrl({ view: 'browser' }),
|
url: renderUrl({ view: 'browser' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'os',
|
key: 'os',
|
||||||
label: formatMessage(labels.os),
|
label: formatMessage(labels.os),
|
||||||
url: makeUrl({ view: 'os' }),
|
url: renderUrl({ view: 'os' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'device',
|
key: 'device',
|
||||||
label: formatMessage(labels.devices),
|
label: formatMessage(labels.devices),
|
||||||
url: makeUrl({ view: 'device' }),
|
url: renderUrl({ view: 'device' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'country',
|
key: 'country',
|
||||||
label: formatMessage(labels.countries),
|
label: formatMessage(labels.countries),
|
||||||
url: makeUrl({ view: 'country' }),
|
url: renderUrl({ view: 'country' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'region',
|
key: 'region',
|
||||||
label: formatMessage(labels.regions),
|
label: formatMessage(labels.regions),
|
||||||
url: makeUrl({ view: 'region' }),
|
url: renderUrl({ view: 'region' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'city',
|
key: 'city',
|
||||||
label: formatMessage(labels.cities),
|
label: formatMessage(labels.cities),
|
||||||
url: makeUrl({ view: 'city' }),
|
url: renderUrl({ view: 'city' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'language',
|
key: 'language',
|
||||||
label: formatMessage(labels.languages),
|
label: formatMessage(labels.languages),
|
||||||
url: makeUrl({ view: 'language' }),
|
url: renderUrl({ view: 'language' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'screen',
|
key: 'screen',
|
||||||
label: formatMessage(labels.screens),
|
label: formatMessage(labels.screens),
|
||||||
url: makeUrl({ view: 'screen' }),
|
url: renderUrl({ view: 'screen' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'event',
|
key: 'event',
|
||||||
label: formatMessage(labels.events),
|
label: formatMessage(labels.events),
|
||||||
url: makeUrl({ view: 'event' }),
|
url: renderUrl({ view: 'event' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'query',
|
key: 'query',
|
||||||
label: formatMessage(labels.queryParameters),
|
label: formatMessage(labels.queryParameters),
|
||||||
url: makeUrl({ view: 'query' }),
|
url: renderUrl({ view: 'query' }),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const DetailsComponent = views[view] || (() => null);
|
const DetailsComponent = views[view] || (() => null);
|
||||||
|
|
||||||
const handleChange = (view: any) => {
|
const handleChange = (view: any) => {
|
||||||
router.push(makeUrl({ view }));
|
router.push(renderUrl({ view }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderValue = (value: string) => items.find(({ key }) => key === value)?.label;
|
const renderValue = (value: string) => items.find(({ key }) => key === value)?.label;
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function WebsiteFilterButton({
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { makeUrl, router } = useNavigation();
|
const { renderUrl, router } = useNavigation();
|
||||||
|
|
||||||
const fieldOptions = [
|
const fieldOptions = [
|
||||||
{ name: 'url', type: 'string', label: formatMessage(labels.url) },
|
{ name: 'url', type: 'string', label: formatMessage(labels.url) },
|
||||||
|
@ -25,7 +25,7 @@ export function WebsiteFilterButton({
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleAddFilter = ({ name, value }) => {
|
const handleAddFilter = ({ name, value }) => {
|
||||||
router.push(makeUrl({ [name]: value }));
|
router.push(renderUrl({ [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { DATA_TYPES } from 'lib/constants';
|
||||||
|
|
||||||
export function EventDataTable({ data = [] }) {
|
export function EventDataTable({ data = [] }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { makeUrl } = useNavigation();
|
const { renderUrl } = useNavigation();
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return <Empty />;
|
return <Empty />;
|
||||||
|
@ -16,7 +16,7 @@ export function EventDataTable({ data = [] }) {
|
||||||
<GridTable data={data}>
|
<GridTable data={data}>
|
||||||
<GridColumn name="eventName" label={formatMessage(labels.event)}>
|
<GridColumn name="eventName" label={formatMessage(labels.event)}>
|
||||||
{row => (
|
{row => (
|
||||||
<Link href={makeUrl({ event: row.eventName })} shallow={true}>
|
<Link href={renderUrl({ event: row.eventName })} shallow={true}>
|
||||||
{row.eventName}
|
{row.eventName}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { DATA_TYPES } from 'lib/constants';
|
||||||
|
|
||||||
export function EventDataValueTable({ data = [], event }: { data: any[]; event: string }) {
|
export function EventDataValueTable({ data = [], event }: { data: any[]; event: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { makeUrl } = useNavigation();
|
const { renderUrl } = useNavigation();
|
||||||
|
|
||||||
const Title = () => {
|
const Title = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link href={makeUrl({ event: undefined })}>
|
<Link href={renderUrl({ event: undefined })}>
|
||||||
<Button>
|
<Button>
|
||||||
<Icon rotate={180}>
|
<Icon rotate={180}>
|
||||||
<Icons.ArrowRight />
|
<Icons.ArrowRight />
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import WebsiteHeader from '../WebsiteHeader';
|
import WebsiteHeader from '../WebsiteHeader';
|
||||||
import WebsiteEventData from './WebsiteEventData';
|
import WebsiteEventData from './WebsiteEventData';
|
||||||
|
|
||||||
export default function WebsiteEventDataPage({ params: { id } }) {
|
export default function WebsiteEventDataPage({ params: { websiteId } }) {
|
||||||
if (!id) {
|
if (!websiteId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WebsiteHeader websiteId={id} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<WebsiteEventData websiteId={id} />
|
<WebsiteEventData websiteId={websiteId} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import WebsiteDetails from './WebsiteDetails';
|
import WebsiteDetails from './WebsiteDetails';
|
||||||
|
|
||||||
export default function WebsitePage({ params: { id } }) {
|
export default function WebsitePage({ params: { websiteId } }) {
|
||||||
return <WebsiteDetails websiteId={id} />;
|
return <WebsiteDetails websiteId={websiteId} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import Realtime from './Realtime';
|
import Realtime from './Realtime';
|
||||||
|
|
||||||
export default function WebsiteRealtimePage({ params: { id } }) {
|
export default function WebsiteRealtimePage({ params: { websiteId } }) {
|
||||||
if (!id) {
|
if (!websiteId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Realtime websiteId={id} />;
|
return <Realtime websiteId={websiteId} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import WebsiteReports from './WebsiteReports';
|
import WebsiteReports from './WebsiteReports';
|
||||||
|
|
||||||
export default function WebsiteReportsPage({ params: { id } }) {
|
export default function WebsiteReportsPage({ params: { websiteId } }) {
|
||||||
if (!id) {
|
if (!websiteId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <WebsiteReports websiteId={id} />;
|
return <WebsiteReports websiteId={websiteId} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function FilterLink({
|
||||||
className,
|
className,
|
||||||
}: FilterLinkProps) {
|
}: FilterLinkProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { makeUrl, query } = useNavigation();
|
const { renderUrl, query } = useNavigation();
|
||||||
const active = query[id] !== undefined;
|
const active = query[id] !== undefined;
|
||||||
const selected = query[id] === value;
|
const selected = query[id] === value;
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export function FilterLink({
|
||||||
{children}
|
{children}
|
||||||
{!value && `(${label || formatMessage(labels.unknown)})`}
|
{!value && `(${label || formatMessage(labels.unknown)})`}
|
||||||
{value && (
|
{value && (
|
||||||
<Link href={makeUrl({ [id]: value })} className={styles.label} replace>
|
<Link href={renderUrl({ [id]: value })} className={styles.label} replace>
|
||||||
{safeDecodeURI(label || value)}
|
{safeDecodeURI(label || value)}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import { useState } from 'react';
|
|
||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
import useFilterQuery from './useFilterQuery';
|
import useFilterQuery from './useFilterQuery';
|
||||||
|
import useCache from 'store/cache';
|
||||||
|
|
||||||
export function useReports(websiteId?: string) {
|
export function useReports({ websiteId, teamId }: { websiteId?: string; teamId?: string }) {
|
||||||
const [modified, setModified] = useState(Date.now());
|
const modified = useCache((state: any) => state?.reports);
|
||||||
const { get, del, useMutation } = useApi();
|
const { get, del, useMutation } = useApi();
|
||||||
const { mutate } = useMutation({ mutationFn: (reportId: string) => del(`/reports/${reportId}`) });
|
|
||||||
const queryResult = useFilterQuery({
|
const queryResult = useFilterQuery({
|
||||||
queryKey: ['reports', { websiteId, modified }],
|
queryKey: ['reports', { websiteId, modified }],
|
||||||
queryFn: (params: any) => {
|
queryFn: (params: any) => {
|
||||||
return get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params);
|
const url = websiteId ? `/websites/${websiteId}/reports` : `/reports`;
|
||||||
|
|
||||||
|
return get(teamId ? `/teams/${teamId}${url}` : url, params);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const { mutate } = useMutation({ mutationFn: (reportId: string) => del(`/reports/${reportId}`) });
|
||||||
|
|
||||||
const deleteReport = (id: any) => {
|
const deleteReport = (reportId: any) => {
|
||||||
mutate(id, {
|
mutate(reportId, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {},
|
||||||
setModified(Date.now());
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
16
src/components/hooks/queries/useTeamMembers.ts
Normal file
16
src/components/hooks/queries/useTeamMembers.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import useApi from './useApi';
|
||||||
|
import useFilterQuery from './useFilterQuery';
|
||||||
|
|
||||||
|
export function useTeamMembers(teamId: string) {
|
||||||
|
const { get } = useApi();
|
||||||
|
|
||||||
|
return useFilterQuery({
|
||||||
|
queryKey: ['teams:users', { teamId }],
|
||||||
|
queryFn: (params: any) => {
|
||||||
|
return get(`/teams/${teamId}/users`, params);
|
||||||
|
},
|
||||||
|
enabled: !!teamId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useTeamMembers;
|
|
@ -10,7 +10,7 @@ const messages = {
|
||||||
'en-US': enUS,
|
'en-US': enUS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const selector = state => state.locale;
|
const selector = (state: { locale: any }) => state.locale;
|
||||||
|
|
||||||
export function useLocale() {
|
export function useLocale() {
|
||||||
const locale = useStore(selector);
|
const locale = useStore(selector);
|
||||||
|
@ -18,7 +18,7 @@ export function useLocale() {
|
||||||
const dir = getTextDirection(locale);
|
const dir = getTextDirection(locale);
|
||||||
const dateLocale = getDateLocale(locale);
|
const dateLocale = getDateLocale(locale);
|
||||||
|
|
||||||
async function loadMessages(locale) {
|
async function loadMessages(locale: string) {
|
||||||
const { ok, data } = await httpGet(`${process.env.basePath}/intl/messages/${locale}.json`);
|
const { ok, data } = await httpGet(`${process.env.basePath}/intl/messages/${locale}.json`);
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
@ -26,7 +26,7 @@ export function useLocale() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveLocale(value) {
|
async function saveLocale(value: string) {
|
||||||
if (!messages[value]) {
|
if (!messages[value]) {
|
||||||
await loadMessages(value);
|
await loadMessages(value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,14 @@ export function useNavigation(): {
|
||||||
pathname: string;
|
pathname: string;
|
||||||
query: { [key: string]: string };
|
query: { [key: string]: string };
|
||||||
router: any;
|
router: any;
|
||||||
makeUrl: (params: any, reset?: boolean) => string;
|
renderUrl: (params: any, reset?: boolean) => string;
|
||||||
|
renderTeamUrl: (url: string) => string;
|
||||||
|
teamId?: string;
|
||||||
} {
|
} {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const params = useSearchParams();
|
const params = useSearchParams();
|
||||||
|
const [, teamId] = pathname.match(/^\/teams\/([a-f0-9-]+)/) || [];
|
||||||
|
|
||||||
const query = useMemo(() => {
|
const query = useMemo(() => {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
|
@ -22,11 +25,15 @@ export function useNavigation(): {
|
||||||
return obj;
|
return obj;
|
||||||
}, [params]);
|
}, [params]);
|
||||||
|
|
||||||
function makeUrl(params: any, reset?: boolean) {
|
function renderUrl(params: any, reset?: boolean) {
|
||||||
return reset ? pathname : buildUrl(pathname, { ...query, ...params });
|
return reset ? pathname : buildUrl(pathname, { ...query, ...params });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { pathname, query, router, makeUrl };
|
function renderTeamUrl(url: string) {
|
||||||
|
return teamId ? `/teams/${teamId}${url}` : url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { pathname, query, router, renderUrl, renderTeamUrl, teamId };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useNavigation;
|
export default useNavigation;
|
||||||
|
|
|
@ -10,7 +10,7 @@ export function FilterTags({ params }) {
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const {
|
const {
|
||||||
router,
|
router,
|
||||||
makeUrl,
|
renderUrl,
|
||||||
query: { view },
|
query: { view },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
|
|
||||||
|
@ -19,11 +19,11 @@ export function FilterTags({ params }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseFilter(param?: string) {
|
function handleCloseFilter(param?: string) {
|
||||||
router.push(makeUrl({ [param]: undefined }));
|
router.push(renderUrl({ [param]: undefined }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResetFilter() {
|
function handleResetFilter() {
|
||||||
router.push(makeUrl({ view }, true));
|
router.push(renderUrl({ view }, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -45,7 +45,7 @@ export function MetricsTable({
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const [{ startDate, endDate, modified }] = useDateRange(websiteId);
|
const [{ startDate, endDate, modified }] = useDateRange(websiteId);
|
||||||
const {
|
const {
|
||||||
makeUrl,
|
renderUrl,
|
||||||
query: { url, referrer, title, os, browser, device, country, region, city },
|
query: { url, referrer, title, os, browser, device, country, region, city },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
@ -142,7 +142,7 @@ export function MetricsTable({
|
||||||
{!data && isLoading && !isFetched && <Loading icon="dots" />}
|
{!data && isLoading && !isFetched && <Loading icon="dots" />}
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
{data && !error && limit && (
|
{data && !error && limit && (
|
||||||
<LinkButton href={makeUrl({ view: type })} variant="quiet">
|
<LinkButton href={renderUrl({ view: type })} variant="quiet">
|
||||||
<Text>{formatMessage(labels.more)}</Text>
|
<Text>{formatMessage(labels.more)}</Text>
|
||||||
<Icon size="sm" rotate={dir === 'rtl' ? 180 : 0}>
|
<Icon size="sm" rotate={dir === 'rtl' ? 180 : 0}>
|
||||||
<Icons.ArrowRight />
|
<Icons.ArrowRight />
|
||||||
|
|
|
@ -12,13 +12,13 @@ export interface PagesTableProps extends MetricsTableProps {
|
||||||
export function PagesTable({ allowFilter, domainName, ...props }: PagesTableProps) {
|
export function PagesTable({ allowFilter, domainName, ...props }: PagesTableProps) {
|
||||||
const {
|
const {
|
||||||
router,
|
router,
|
||||||
makeUrl,
|
renderUrl,
|
||||||
query: { view = 'url' },
|
query: { view = 'url' },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const handleSelect = (key: any) => {
|
const handleSelect = (key: any) => {
|
||||||
router.push(makeUrl({ view: key }), { scroll: true });
|
router.push(renderUrl({ view: key }), { scroll: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { User, Website } from '@prisma/client';
|
import { User, Website } from '@prisma/client';
|
||||||
import redis from '@umami/redis-client';
|
import redis from '@umami/redis-client';
|
||||||
import { getSession, getUserById, getWebsiteById } from '../queries';
|
import { getSession, getUser, getWebsite } from '../queries';
|
||||||
|
|
||||||
async function fetchWebsite(id): Promise<Website> {
|
async function fetchWebsite(websiteId: string): Promise<Website> {
|
||||||
return redis.client.getCache(`website:${id}`, () => getWebsiteById(id), 86400);
|
return redis.client.getCache(`website:${websiteId}`, () => getWebsite(websiteId), 86400);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeWebsite(data) {
|
async function storeWebsite(data: { id: any }) {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
const key = `website:${id}`;
|
const key = `website:${id}`;
|
||||||
|
|
||||||
|
@ -21,11 +21,7 @@ async function deleteWebsite(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUser(id): Promise<User> {
|
async function fetchUser(id): Promise<User> {
|
||||||
return redis.client.getCache(
|
return redis.client.getCache(`user:${id}`, () => getUser(id, { includePassword: true }), 86400);
|
||||||
`user:${id}`,
|
|
||||||
() => getUserById(id, { includePassword: true }),
|
|
||||||
86400,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeUser(data) {
|
async function storeUser(data) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import cache from 'lib/cache';
|
import cache from 'lib/cache';
|
||||||
import { getSession, getUserById, getWebsiteById } from 'queries';
|
import { getSession, getUser, getWebsite } from 'queries';
|
||||||
import { User, Website, Session } from '@prisma/client';
|
import { User, Website, Session } from '@prisma/client';
|
||||||
|
|
||||||
export async function loadWebsite(websiteId: string): Promise<Website> {
|
export async function loadWebsite(websiteId: string): Promise<Website> {
|
||||||
|
@ -8,7 +8,7 @@ export async function loadWebsite(websiteId: string): Promise<Website> {
|
||||||
if (cache.enabled) {
|
if (cache.enabled) {
|
||||||
website = await cache.fetchWebsite(websiteId);
|
website = await cache.fetchWebsite(websiteId);
|
||||||
} else {
|
} else {
|
||||||
website = await getWebsiteById(websiteId);
|
website = await getWebsite(websiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!website || website.deletedAt) {
|
if (!website || website.deletedAt) {
|
||||||
|
@ -40,7 +40,7 @@ export async function loadUser(userId: string): Promise<User> {
|
||||||
if (cache.enabled) {
|
if (cache.enabled) {
|
||||||
user = await cache.fetchUser(userId);
|
user = await cache.fetchUser(userId);
|
||||||
} else {
|
} else {
|
||||||
user = await getUserById(userId);
|
user = await getUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user || user.deletedAt) {
|
if (!user || user.deletedAt) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
unauthorized,
|
unauthorized,
|
||||||
} from 'next-basics';
|
} from 'next-basics';
|
||||||
import { NextApiRequestCollect } from 'pages/api/send';
|
import { NextApiRequestCollect } from 'pages/api/send';
|
||||||
import { getUserById } from '../queries';
|
import { getUser } from '../queries';
|
||||||
|
|
||||||
const log = debug('umami:middleware');
|
const log = debug('umami:middleware');
|
||||||
|
|
||||||
|
@ -57,12 +57,12 @@ export const useAuth = createMiddleware(async (req, res, next) => {
|
||||||
const { userId, authKey, grant } = payload || {};
|
const { userId, authKey, grant } = payload || {};
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
user = await getUserById(userId);
|
user = await getUser(userId);
|
||||||
} else if (redis.enabled && authKey) {
|
} else if (redis.enabled && authKey) {
|
||||||
const key = await redis.client.get(authKey);
|
const key = await redis.client.get(authKey);
|
||||||
|
|
||||||
if (key?.userId) {
|
if (key?.userId) {
|
||||||
user = await getUserById(key.userId);
|
user = await getUser(key.userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,24 +175,12 @@ async function rawQuery(sql: string, data: object): Promise<any> {
|
||||||
return prisma.rawQuery(query, params);
|
return prisma.rawQuery(query, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPageFilters(filters: SearchFilter): [
|
async function pagedQuery<T>(model: string, criteria: T, filters: SearchFilter) {
|
||||||
{
|
|
||||||
orderBy: {
|
|
||||||
[x: string]: string;
|
|
||||||
}[];
|
|
||||||
take: number;
|
|
||||||
skip: number;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageSize: number;
|
|
||||||
page: number;
|
|
||||||
orderBy: string;
|
|
||||||
},
|
|
||||||
] {
|
|
||||||
const { page = 1, pageSize = DEFAULT_PAGE_SIZE, orderBy, sortDescending = false } = filters || {};
|
const { page = 1, pageSize = DEFAULT_PAGE_SIZE, orderBy, sortDescending = false } = filters || {};
|
||||||
|
|
||||||
return [
|
const data = await prisma.client[model].findMany({
|
||||||
{
|
...criteria,
|
||||||
|
...{
|
||||||
...(pageSize > 0 && { take: +pageSize, skip: +pageSize * (page - 1) }),
|
...(pageSize > 0 && { take: +pageSize, skip: +pageSize * (page - 1) }),
|
||||||
...(orderBy && {
|
...(orderBy && {
|
||||||
orderBy: [
|
orderBy: [
|
||||||
|
@ -202,8 +190,11 @@ function getPageFilters(filters: SearchFilter): [
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{ page: +page, pageSize, orderBy },
|
});
|
||||||
];
|
|
||||||
|
const count = await prisma.client[model].count({ where: (criteria as any).where });
|
||||||
|
|
||||||
|
return { data, count, page: +page, pageSize, orderBy };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getQueryMode(): Prisma.QueryMode {
|
function getQueryMode(): Prisma.QueryMode {
|
||||||
|
@ -225,7 +216,7 @@ export default {
|
||||||
getTimestampDiffQuery,
|
getTimestampDiffQuery,
|
||||||
getFilterQuery,
|
getFilterQuery,
|
||||||
parseFilters,
|
parseFilters,
|
||||||
getPageFilters,
|
|
||||||
getQueryMode,
|
getQueryMode,
|
||||||
rawQuery,
|
rawQuery,
|
||||||
|
pagedQuery,
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,6 +38,10 @@ export interface TeamSearchFilter extends SearchFilter {
|
||||||
userId?: string;
|
userId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TeamUserSearchFilter extends SearchFilter {
|
||||||
|
teamId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReportSearchFilter extends SearchFilter {
|
export interface ReportSearchFilter extends SearchFilter {
|
||||||
userId?: string;
|
userId?: string;
|
||||||
websiteId?: string;
|
websiteId?: string;
|
||||||
|
|
|
@ -42,10 +42,7 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page, query, pageSize } = req.query;
|
|
||||||
|
|
||||||
const users = await getUsers(
|
const users = await getUsers(
|
||||||
{ page, query, pageSize: +pageSize || undefined },
|
|
||||||
{
|
{
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
|
@ -57,6 +54,7 @@ export default async (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
req.query,
|
||||||
);
|
);
|
||||||
|
|
||||||
return ok(res, users);
|
return ok(res, users);
|
||||||
|
|
|
@ -39,25 +39,19 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const websites = await getWebsites(req.query, {
|
const websites = await getWebsites(
|
||||||
include: {
|
{
|
||||||
teamWebsite: {
|
include: {
|
||||||
include: {
|
user: {
|
||||||
team: {
|
select: {
|
||||||
select: {
|
username: true,
|
||||||
name: true,
|
id: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
username: true,
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
req.query,
|
||||||
|
);
|
||||||
|
|
||||||
return ok(res, websites);
|
return ok(res, websites);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,16 @@ import { NextApiRequestAuth } from 'lib/types';
|
||||||
import { useAuth } from 'lib/middleware';
|
import { useAuth } from 'lib/middleware';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { ok } from 'next-basics';
|
import { ok } from 'next-basics';
|
||||||
|
import { getUserTeams } from 'queries/admin/team';
|
||||||
|
|
||||||
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
|
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
return ok(res, req.auth.user);
|
const { user } = req.auth;
|
||||||
|
|
||||||
|
const teams = await getUserTeams(user.id);
|
||||||
|
|
||||||
|
user['teams'] = teams.data.map(n => n);
|
||||||
|
|
||||||
|
return ok(res, user);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
methodNotAllowed,
|
methodNotAllowed,
|
||||||
ok,
|
ok,
|
||||||
} from 'next-basics';
|
} from 'next-basics';
|
||||||
import { getUserById, updateUser } from 'queries';
|
import { getUser, updateUser } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface UserPasswordRequestQuery {
|
export interface UserPasswordRequestQuery {
|
||||||
|
@ -43,7 +43,7 @@ export default async (
|
||||||
const { id } = req.auth.user;
|
const { id } = req.auth.user;
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const user = await getUserById(id, { includePassword: true });
|
const user = await getUser(id, { includePassword: true });
|
||||||
|
|
||||||
if (!checkPassword(currentPassword, user.password)) {
|
if (!checkPassword(currentPassword, user.password)) {
|
||||||
return badRequest(res, 'Current password is incorrect');
|
return badRequest(res, 'Current password is incorrect');
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, ReportType, YupRequest } from 'lib/types';
|
import { NextApiRequestQueryBody, ReportType, YupRequest } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { deleteReport, getReportById, updateReport } from 'queries';
|
import { deleteReport, getReport, updateReport } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface ReportRequestQuery {
|
export interface ReportRequestQuery {
|
||||||
|
@ -54,7 +54,7 @@ export default async (
|
||||||
} = req.auth;
|
} = req.auth;
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
const report = await getReportById(reportId);
|
const report = await getReport(reportId);
|
||||||
|
|
||||||
if (!(await canViewReport(req.auth, report))) {
|
if (!(await canViewReport(req.auth, report))) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
|
@ -68,7 +68,7 @@ export default async (
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { websiteId, type, name, description, parameters } = req.body;
|
const { websiteId, type, name, description, parameters } = req.body;
|
||||||
|
|
||||||
const report = await getReportById(reportId);
|
const report = await getReport(reportId);
|
||||||
|
|
||||||
if (!(await canUpdateReport(req.auth, report))) {
|
if (!(await canUpdateReport(req.auth, report))) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
|
@ -87,7 +87,7 @@ export default async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'DELETE') {
|
if (req.method === 'DELETE') {
|
||||||
const report = await getReportById(reportId);
|
const report = await getReport(reportId);
|
||||||
|
|
||||||
if (!(await canDeleteReport(req.auth, report))) {
|
if (!(await canDeleteReport(req.auth, report))) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok } from 'next-basics';
|
import { methodNotAllowed, ok } from 'next-basics';
|
||||||
import { createReport, getReportsByUserId } from 'queries';
|
import { createReport, getUserReports } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface ReportsRequestQuery extends SearchFilter {}
|
export interface ReportsRequestQuery extends SearchFilter {}
|
||||||
|
@ -52,11 +52,10 @@ export default async (
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
const { page, query, pageSize } = req.query;
|
const { page, query, pageSize } = req.query;
|
||||||
|
|
||||||
const data = await getReportsByUserId(userId, {
|
const data = await getUserReports(userId, {
|
||||||
page,
|
page,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
query,
|
query,
|
||||||
includeTeams: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return ok(res, data);
|
return ok(res, data);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody } from 'lib/types';
|
import { NextApiRequestQueryBody } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { createToken, methodNotAllowed, notFound, ok } from 'next-basics';
|
import { createToken, methodNotAllowed, notFound, ok } from 'next-basics';
|
||||||
import { getWebsiteByShareId } from 'queries';
|
import { getSharedWebsite } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface ShareRequestQuery {
|
export interface ShareRequestQuery {
|
||||||
|
@ -30,7 +30,7 @@ export default async (
|
||||||
const { id: shareId } = req.query;
|
const { id: shareId } = req.query;
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
const website = await getWebsiteByShareId(shareId);
|
const website = await getSharedWebsite(shareId);
|
||||||
|
|
||||||
if (website) {
|
if (website) {
|
||||||
const data = { websiteId: website.id };
|
const data = { websiteId: website.id };
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody } from 'lib/types';
|
import { NextApiRequestQueryBody } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, notFound, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, notFound, ok, unauthorized } from 'next-basics';
|
||||||
import { deleteTeam, getTeamById, updateTeam } from 'queries';
|
import { deleteTeam, getTeam, updateTeam } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface TeamRequestQuery {
|
export interface TeamRequestQuery {
|
||||||
|
@ -44,7 +44,7 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const team = await getTeamById(teamId, { includeTeamUser: true });
|
const team = await getTeam(teamId, { includeMembers: true });
|
||||||
|
|
||||||
if (!team) {
|
if (!team) {
|
||||||
return notFound(res);
|
return notFound(res);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createTeamUser, getTeamUser, getUsersByTeamId } from 'queries';
|
import { createTeamUser, getTeamUser, getTeamUsers } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface TeamUserRequestQuery extends SearchFilter {
|
export interface TeamUserRequestQuery extends SearchFilter {
|
||||||
|
@ -46,7 +46,7 @@ export default async (
|
||||||
|
|
||||||
const { query, page, pageSize } = req.query;
|
const { query, page, pageSize } = req.query;
|
||||||
|
|
||||||
const users = await getUsersByTeamId(teamId, {
|
const users = await getTeamUsers(teamId, {
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createWebsite, getWebsitesByTeamId } from 'queries';
|
import { createWebsite, getTeamWebsites } from 'queries';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
|
|
||||||
export interface TeamWebsiteRequestQuery extends SearchFilter {
|
export interface TeamWebsiteRequestQuery extends SearchFilter {
|
||||||
|
@ -46,7 +46,7 @@ export default async (
|
||||||
|
|
||||||
const { page, query, pageSize } = req.query;
|
const { page, query, pageSize } = req.query;
|
||||||
|
|
||||||
const websites = await getWebsitesByTeamId(teamId, {
|
const websites = await getTeamWebsites(teamId, {
|
||||||
page,
|
page,
|
||||||
query,
|
query,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createTeam, getTeamsByUserId } from 'queries';
|
import { createTeam, getUserTeams } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface TeamsRequestQuery extends SearchFilter {}
|
export interface TeamsRequestQuery extends SearchFilter {}
|
||||||
|
@ -37,7 +37,7 @@ export default async (
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
const { page, query, pageSize } = req.query;
|
const { page, query, pageSize } = req.query;
|
||||||
|
|
||||||
const results = await getTeamsByUserId(userId, {
|
const results = await getUserTeams(userId, {
|
||||||
page,
|
page,
|
||||||
query,
|
query,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody } from 'lib/types';
|
import { NextApiRequestQueryBody } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, notFound, ok } from 'next-basics';
|
import { methodNotAllowed, notFound, ok } from 'next-basics';
|
||||||
import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries';
|
import { createTeamUser, findTeam, getTeamUser } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
export interface TeamsJoinRequestBody {
|
export interface TeamsJoinRequestBody {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
|
@ -26,7 +26,11 @@ export default async (
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { accessCode } = req.body;
|
const { accessCode } = req.body;
|
||||||
|
|
||||||
const team = await getTeamByAccessCode(accessCode);
|
const team = await findTeam({
|
||||||
|
where: {
|
||||||
|
accessCode,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!team) {
|
if (!team) {
|
||||||
return notFound(res, 'message.team-not-found');
|
return notFound(res, 'message.team-not-found');
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, Role, User } from 'lib/types';
|
import { NextApiRequestQueryBody, Role, User } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { deleteUser, getUserById, getUserByUsername, updateUser } from 'queries';
|
import { deleteUser, getUser, getUserByUsername, updateUser } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface UserRequestQuery {
|
export interface UserRequestQuery {
|
||||||
|
@ -45,7 +45,7 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await getUserById(id);
|
const user = await getUser(id);
|
||||||
|
|
||||||
return ok(res, user);
|
return ok(res, user);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export default async (
|
||||||
|
|
||||||
const { username, password, role } = req.body;
|
const { username, password, role } = req.body;
|
||||||
|
|
||||||
const user = await getUserById(id);
|
const user = await getUser(id);
|
||||||
|
|
||||||
const data: any = {};
|
const data: any = {};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getTeamsByUserId } from 'queries';
|
import { getUserTeams } from 'queries';
|
||||||
|
|
||||||
export interface UserTeamsRequestQuery extends SearchFilter {
|
export interface UserTeamsRequestQuery extends SearchFilter {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -41,7 +41,7 @@ export default async (
|
||||||
|
|
||||||
const { page, query, pageSize } = req.query;
|
const { page, query, pageSize } = req.query;
|
||||||
|
|
||||||
const teams = await getTeamsByUserId(userId, {
|
const teams = await getUserTeams(userId, {
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody } from 'lib/types';
|
import { NextApiRequestQueryBody } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getEventDataUsage, getEventUsage, getUserWebsites } from 'queries';
|
import { getAllWebsites, getEventDataUsage, getEventUsage } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface UserUsageRequestQuery {
|
export interface UserUsageRequestQuery {
|
||||||
|
@ -26,7 +26,7 @@ const schema = {
|
||||||
GET: yup.object().shape({
|
GET: yup.object().shape({
|
||||||
id: yup.string().uuid().required(),
|
id: yup.string().uuid().required(),
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
|
endAt: yup.number().integer().moreThan(yup.ref<number>('startAt')).required(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export default async (
|
||||||
const startDate = new Date(+startAt);
|
const startDate = new Date(+startAt);
|
||||||
const endDate = new Date(+endAt);
|
const endDate = new Date(+endAt);
|
||||||
|
|
||||||
const websites = await getUserWebsites(userId);
|
const websites = await getAllWebsites(userId);
|
||||||
|
|
||||||
const websiteIds = websites.map(a => a.id);
|
const websiteIds = websites.map(a => a.id);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getWebsitesByUserId } from 'queries';
|
import { getUserWebsites } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface UserWebsitesRequestQuery extends SearchFilter {
|
export interface UserWebsitesRequestQuery extends SearchFilter {
|
||||||
|
@ -37,7 +37,7 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const websites = await getWebsitesByUserId(userId, {
|
const websites = await getUserWebsites(userId, {
|
||||||
page,
|
page,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
query,
|
query,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics';
|
||||||
import { Website, NextApiRequestQueryBody } from 'lib/types';
|
import { Website, NextApiRequestQueryBody } from 'lib/types';
|
||||||
import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth';
|
import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth';
|
||||||
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { deleteWebsite, getWebsiteById, updateWebsite } from 'queries';
|
import { deleteWebsite, getWebsite, updateWebsite } from 'queries';
|
||||||
import { SHARE_ID_REGEX } from 'lib/constants';
|
import { SHARE_ID_REGEX } from 'lib/constants';
|
||||||
|
|
||||||
export interface WebsiteRequestQuery {
|
export interface WebsiteRequestQuery {
|
||||||
|
@ -45,7 +45,7 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const website = await getWebsiteById(websiteId);
|
const website = await getWebsite(websiteId);
|
||||||
|
|
||||||
return ok(res, website);
|
return ok(res, website);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getReportsByWebsiteId } from 'queries';
|
import { getWebsiteReports } from 'queries';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
|
|
||||||
export interface ReportsRequestQuery extends SearchFilter {
|
export interface ReportsRequestQuery extends SearchFilter {
|
||||||
|
@ -35,7 +35,7 @@ export default async (
|
||||||
|
|
||||||
const { page, query, pageSize } = req.query;
|
const { page, query, pageSize } = req.query;
|
||||||
|
|
||||||
const data = await getReportsByWebsiteId(websiteId, {
|
const data = await getWebsiteReports(websiteId, {
|
||||||
page,
|
page,
|
||||||
pageSize: +pageSize || undefined,
|
pageSize: +pageSize || undefined,
|
||||||
query,
|
query,
|
||||||
|
|
|
@ -1,39 +1,30 @@
|
||||||
import { Prisma, Report } from '@prisma/client';
|
import { Prisma, Report } from '@prisma/client';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, ReportSearchFilter } from 'lib/types';
|
import { FilterResult, ReportSearchFilter } from 'lib/types';
|
||||||
|
import ReportFindUniqueArgs = Prisma.ReportFindUniqueArgs;
|
||||||
|
import ReportFindManyArgs = Prisma.ReportFindManyArgs;
|
||||||
|
|
||||||
export async function createReport(data: Prisma.ReportUncheckedCreateInput): Promise<Report> {
|
async function findReport(criteria: ReportFindUniqueArgs) {
|
||||||
return prisma.client.report.create({ data });
|
return prisma.client.report.findUnique(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReportById(reportId: string): Promise<Report> {
|
export async function getReport(reportId: string): Promise<Report> {
|
||||||
return prisma.client.report.findUnique({
|
return findReport({
|
||||||
where: {
|
where: {
|
||||||
id: reportId,
|
id: reportId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateReport(
|
|
||||||
reportId: string,
|
|
||||||
data: Prisma.ReportUpdateInput,
|
|
||||||
): Promise<Report> {
|
|
||||||
return prisma.client.report.update({ where: { id: reportId }, data });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteReport(reportId: string): Promise<Report> {
|
|
||||||
return prisma.client.report.delete({ where: { id: reportId } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getReports(
|
export async function getReports(
|
||||||
params: ReportSearchFilter,
|
criteria: ReportFindManyArgs,
|
||||||
options?: { include?: Prisma.ReportInclude },
|
filters: ReportSearchFilter = {},
|
||||||
): Promise<FilterResult<Report[]>> {
|
): Promise<FilterResult<Report[]>> {
|
||||||
const { query, userId, websiteId } = params;
|
|
||||||
|
|
||||||
const mode = prisma.getQueryMode();
|
const mode = prisma.getQueryMode();
|
||||||
|
const { query, userId, websiteId } = filters;
|
||||||
|
|
||||||
const where: Prisma.ReportWhereInput = {
|
const where: Prisma.ReportWhereInput = {
|
||||||
|
...criteria.where,
|
||||||
userId,
|
userId,
|
||||||
websiteId,
|
websiteId,
|
||||||
AND: [
|
AND: [
|
||||||
|
@ -93,32 +84,18 @@ export async function getReports(
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const [pageFilters, pageInfo] = prisma.getPageFilters(params);
|
return prisma.pagedQuery('report', { where }, filters);
|
||||||
|
|
||||||
const reports = await prisma.client.report.findMany({
|
|
||||||
where,
|
|
||||||
...pageFilters,
|
|
||||||
...(options?.include && { include: options.include }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await prisma.client.report.count({
|
|
||||||
where,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: reports,
|
|
||||||
count,
|
|
||||||
...pageInfo,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReportsByUserId(
|
export async function getUserReports(
|
||||||
userId: string,
|
userId: string,
|
||||||
filter?: ReportSearchFilter,
|
filters?: ReportSearchFilter,
|
||||||
): Promise<FilterResult<Report[]>> {
|
): Promise<FilterResult<Report[]>> {
|
||||||
return getReports(
|
return getReports(
|
||||||
{ userId, ...filter },
|
|
||||||
{
|
{
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
website: {
|
website: {
|
||||||
select: {
|
select: {
|
||||||
|
@ -128,12 +105,35 @@ export async function getReportsByUserId(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
filters,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReportsByWebsiteId(
|
export async function getWebsiteReports(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filter: ReportSearchFilter,
|
filters: ReportSearchFilter = {},
|
||||||
): Promise<FilterResult<Report[]>> {
|
): Promise<FilterResult<Report[]>> {
|
||||||
return getReports({ websiteId, ...filter });
|
return getReports(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
websiteId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filters,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createReport(data: Prisma.ReportUncheckedCreateInput): Promise<Report> {
|
||||||
|
return prisma.client.report.create({ data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateReport(
|
||||||
|
reportId: string,
|
||||||
|
data: Prisma.ReportUpdateInput,
|
||||||
|
): Promise<Report> {
|
||||||
|
return prisma.client.report.update({ where: { id: reportId }, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteReport(reportId: string): Promise<Report> {
|
||||||
|
return prisma.client.report.delete({ where: { id: reportId } });
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,29 +3,109 @@ import { ROLES } from 'lib/constants';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, TeamSearchFilter } from 'lib/types';
|
import { FilterResult, TeamSearchFilter } from 'lib/types';
|
||||||
|
import TeamFindManyArgs = Prisma.TeamFindManyArgs;
|
||||||
|
|
||||||
export interface GetTeamOptions {
|
export async function findTeam(criteria: Prisma.TeamFindFirstArgs): Promise<Team> {
|
||||||
includeTeamUser?: boolean;
|
return prisma.client.team.findFirst(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTeam(where: Prisma.TeamWhereInput, options: GetTeamOptions = {}): Promise<Team> {
|
export async function getTeam(teamId: string, options: { includeMembers?: boolean } = {}) {
|
||||||
const { includeTeamUser = false } = options;
|
const { includeMembers } = options;
|
||||||
const { client } = prisma;
|
|
||||||
|
|
||||||
return client.team.findFirst({
|
return findTeam({
|
||||||
where,
|
where: {
|
||||||
include: {
|
id: teamId,
|
||||||
teamUser: includeTeamUser,
|
|
||||||
},
|
},
|
||||||
|
...(includeMembers && { include: { teamUser: true } }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTeamById(teamId: string, options: GetTeamOptions = {}) {
|
export async function getTeams(
|
||||||
return getTeam({ id: teamId }, options);
|
criteria: TeamFindManyArgs,
|
||||||
|
filters: TeamSearchFilter = {},
|
||||||
|
): Promise<FilterResult<Team[]>> {
|
||||||
|
const mode = prisma.getQueryMode();
|
||||||
|
const { userId, query } = filters;
|
||||||
|
|
||||||
|
const where: Prisma.TeamWhereInput = {
|
||||||
|
...criteria.where,
|
||||||
|
...(userId && {
|
||||||
|
teamUser: {
|
||||||
|
some: { userId },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...(query && {
|
||||||
|
AND: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
startsWith: query,
|
||||||
|
mode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
teamUser: {
|
||||||
|
some: {
|
||||||
|
role: ROLES.teamOwner,
|
||||||
|
user: {
|
||||||
|
username: {
|
||||||
|
startsWith: query,
|
||||||
|
mode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return prisma.pagedQuery<TeamFindManyArgs>(
|
||||||
|
'team',
|
||||||
|
{
|
||||||
|
...criteria,
|
||||||
|
where,
|
||||||
|
},
|
||||||
|
filters,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTeamByAccessCode(accessCode: string, options: GetTeamOptions = {}) {
|
export async function getUserTeams(userId: string, filters: TeamSearchFilter = {}) {
|
||||||
return getTeam({ accessCode }, options);
|
return getTeams(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
teamUser: {
|
||||||
|
some: { userId },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
teamUser: {
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
website: {
|
||||||
|
where: { deletedAt: null },
|
||||||
|
},
|
||||||
|
teamUser: {
|
||||||
|
where: {
|
||||||
|
user: { deletedAt: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filters,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTeam(data: Prisma.TeamCreateInput, userId: string): Promise<any> {
|
export async function createTeam(data: Prisma.TeamCreateInput, userId: string): Promise<any> {
|
||||||
|
@ -93,94 +173,3 @@ export async function deleteTeam(
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTeams(
|
|
||||||
filters: TeamSearchFilter,
|
|
||||||
options?: { include?: Prisma.TeamInclude },
|
|
||||||
): Promise<FilterResult<Team[]>> {
|
|
||||||
const { userId, query } = filters;
|
|
||||||
const mode = prisma.getQueryMode();
|
|
||||||
const { client } = prisma;
|
|
||||||
|
|
||||||
const where: Prisma.TeamWhereInput = {
|
|
||||||
...(userId && {
|
|
||||||
teamUser: {
|
|
||||||
some: { userId },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...(query && {
|
|
||||||
AND: {
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
name: { startsWith: query, mode },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
teamUser: {
|
|
||||||
some: {
|
|
||||||
role: ROLES.teamOwner,
|
|
||||||
user: {
|
|
||||||
username: {
|
|
||||||
startsWith: query,
|
|
||||||
mode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const [pageFilters, getParameters] = prisma.getPageFilters({
|
|
||||||
orderBy: 'name',
|
|
||||||
...filters,
|
|
||||||
});
|
|
||||||
|
|
||||||
const teams = await client.team.findMany({
|
|
||||||
where: {
|
|
||||||
...where,
|
|
||||||
},
|
|
||||||
...pageFilters,
|
|
||||||
...(options?.include && { include: options?.include }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await client.team.count({ where });
|
|
||||||
|
|
||||||
return { data: teams, count, ...getParameters };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTeamsByUserId(
|
|
||||||
userId: string,
|
|
||||||
filter?: TeamSearchFilter,
|
|
||||||
): Promise<FilterResult<Team[]>> {
|
|
||||||
return getTeams(
|
|
||||||
{ userId, ...filter },
|
|
||||||
{
|
|
||||||
include: {
|
|
||||||
teamUser: {
|
|
||||||
include: {
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_count: {
|
|
||||||
select: {
|
|
||||||
website: {
|
|
||||||
where: { deletedAt: null },
|
|
||||||
},
|
|
||||||
teamUser: {
|
|
||||||
where: {
|
|
||||||
user: { deletedAt: null },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import { Prisma, TeamUser } from '@prisma/client';
|
import { TeamUser } from '@prisma/client';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
|
import { FilterResult, TeamUserSearchFilter } from 'lib/types';
|
||||||
export async function getTeamUserById(teamUserId: string): Promise<TeamUser> {
|
|
||||||
return prisma.client.teamUser.findUnique({
|
|
||||||
where: {
|
|
||||||
id: teamUserId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTeamUser(teamId: string, userId: string): Promise<TeamUser> {
|
export async function getTeamUser(teamId: string, userId: string): Promise<TeamUser> {
|
||||||
return prisma.client.teamUser.findFirst({
|
return prisma.client.teamUser.findFirst({
|
||||||
|
@ -21,20 +14,25 @@ export async function getTeamUser(teamId: string, userId: string): Promise<TeamU
|
||||||
|
|
||||||
export async function getTeamUsers(
|
export async function getTeamUsers(
|
||||||
teamId: string,
|
teamId: string,
|
||||||
): Promise<(TeamUser & { user: { id: string; username: string } })[]> {
|
filters?: TeamUserSearchFilter,
|
||||||
return prisma.client.teamUser.findMany({
|
): Promise<FilterResult<TeamUser[]>> {
|
||||||
where: {
|
return prisma.pagedQuery(
|
||||||
teamId,
|
'teamUser',
|
||||||
},
|
{
|
||||||
include: {
|
where: {
|
||||||
user: {
|
teamId,
|
||||||
select: {
|
},
|
||||||
id: true,
|
include: {
|
||||||
username: true,
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
filters,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTeamUser(
|
export async function createTeamUser(
|
||||||
|
@ -52,18 +50,6 @@ export async function createTeamUser(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTeamUser(
|
|
||||||
teamUserId: string,
|
|
||||||
data: Prisma.TeamUserUpdateInput,
|
|
||||||
): Promise<TeamUser> {
|
|
||||||
return prisma.client.teamUser.update({
|
|
||||||
where: {
|
|
||||||
id: teamUserId,
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteTeamUser(teamId: string, userId: string): Promise<TeamUser> {
|
export async function deleteTeamUser(teamId: string, userId: string): Promise<TeamUser> {
|
||||||
const { client } = prisma;
|
const { client } = prisma;
|
||||||
|
|
||||||
|
@ -74,15 +60,3 @@ export async function deleteTeamUser(teamId: string, userId: string): Promise<Te
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTeamUserByUserId(
|
|
||||||
userId: string,
|
|
||||||
teamId: string,
|
|
||||||
): Promise<Prisma.BatchPayload> {
|
|
||||||
return prisma.client.teamUser.deleteMany({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,24 +4,25 @@ import { ROLES } from 'lib/constants';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, Role, User, UserSearchFilter } from 'lib/types';
|
import { FilterResult, Role, User, UserSearchFilter } from 'lib/types';
|
||||||
import { getRandomChars } from 'next-basics';
|
import { getRandomChars } from 'next-basics';
|
||||||
|
import UserFindManyArgs = Prisma.UserFindManyArgs;
|
||||||
|
|
||||||
export interface GetUserOptions {
|
export interface GetUserOptions {
|
||||||
includePassword?: boolean;
|
includePassword?: boolean;
|
||||||
showDeleted?: boolean;
|
showDeleted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUser(
|
async function findUser(
|
||||||
where: Prisma.UserWhereUniqueInput,
|
criteria: Prisma.UserFindUniqueArgs,
|
||||||
options: GetUserOptions = {},
|
options: GetUserOptions = {},
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const { includePassword = false, showDeleted = false } = options;
|
const { includePassword = false, showDeleted = false } = options;
|
||||||
|
|
||||||
if (showDeleted) {
|
|
||||||
where.deletedAt = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return prisma.client.user.findUnique({
|
return prisma.client.user.findUnique({
|
||||||
where,
|
...criteria,
|
||||||
|
where: {
|
||||||
|
...criteria.where,
|
||||||
|
...(showDeleted && { delatedAt: null }),
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
|
@ -32,19 +33,26 @@ async function getUser(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserById(id: string, options: GetUserOptions = {}) {
|
export async function getUser(userId: string, options: GetUserOptions = {}) {
|
||||||
return getUser({ id }, options);
|
return findUser(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserByUsername(username: string, options: GetUserOptions = {}) {
|
export async function getUserByUsername(username: string, options: GetUserOptions = {}) {
|
||||||
return getUser({ username }, options);
|
return findUser({ where: { username } }, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsers(
|
export async function getUsers(
|
||||||
params: UserSearchFilter,
|
criteria: UserFindManyArgs,
|
||||||
options?: { include?: Prisma.UserInclude },
|
filters?: UserSearchFilter,
|
||||||
): Promise<FilterResult<User[]>> {
|
): Promise<FilterResult<User[]>> {
|
||||||
const { teamId, query } = params;
|
const { teamId, query } = filters;
|
||||||
const mode = prisma.getQueryMode();
|
const mode = prisma.getQueryMode();
|
||||||
|
|
||||||
const where: Prisma.UserWhereInput = {
|
const where: Prisma.UserWhereInput = {
|
||||||
|
@ -67,49 +75,19 @@ export async function getUsers(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
deletedAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [pageFilters, getParameters] = prisma.getPageFilters({
|
return prisma.pagedQuery(
|
||||||
orderBy: 'createdAt',
|
'user',
|
||||||
sortDescending: true,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
const users = await prisma.client.user
|
|
||||||
.findMany({
|
|
||||||
where: {
|
|
||||||
...where,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
...pageFilters,
|
|
||||||
...(options?.include && { include: options.include }),
|
|
||||||
})
|
|
||||||
.then((a: { [x: string]: any; password: any }[]) => {
|
|
||||||
return a.map(({ password, ...rest }) => rest);
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await prisma.client.user.count({
|
|
||||||
where: {
|
|
||||||
...where,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { data: users as any, count, ...getParameters };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUsersByTeamId(teamId: string, filter?: UserSearchFilter) {
|
|
||||||
return getUsers(
|
|
||||||
{ teamId, ...filter },
|
|
||||||
{
|
{
|
||||||
include: {
|
...criteria,
|
||||||
teamUser: {
|
where,
|
||||||
select: {
|
},
|
||||||
teamId: true,
|
{
|
||||||
role: true,
|
orderBy: 'createdAt',
|
||||||
},
|
sortDescending: true,
|
||||||
},
|
...filters,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,29 +2,37 @@ import { Prisma, Website } from '@prisma/client';
|
||||||
import cache from 'lib/cache';
|
import cache from 'lib/cache';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, WebsiteSearchFilter } from 'lib/types';
|
import { FilterResult, WebsiteSearchFilter } from 'lib/types';
|
||||||
|
import WebsiteFindManyArgs = Prisma.WebsiteFindManyArgs;
|
||||||
|
|
||||||
async function getWebsite(where: Prisma.WebsiteWhereUniqueInput): Promise<Website> {
|
async function findWebsite(criteria: Prisma.WebsiteFindManyArgs): Promise<Website> {
|
||||||
return prisma.client.website.findUnique({
|
return prisma.client.website.findUnique(criteria);
|
||||||
where,
|
}
|
||||||
|
|
||||||
|
export async function getWebsite(websiteId: string) {
|
||||||
|
return findWebsite({
|
||||||
|
where: {
|
||||||
|
id: websiteId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWebsiteById(id: string) {
|
export async function getSharedWebsite(shareId: string) {
|
||||||
return getWebsite({ id });
|
return findWebsite({
|
||||||
}
|
where: {
|
||||||
|
shareId,
|
||||||
export async function getWebsiteByShareId(shareId: string) {
|
},
|
||||||
return getWebsite({ shareId });
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWebsites(
|
export async function getWebsites(
|
||||||
|
criteria: WebsiteFindManyArgs,
|
||||||
filters: WebsiteSearchFilter,
|
filters: WebsiteSearchFilter,
|
||||||
options?: { include?: Prisma.WebsiteInclude },
|
|
||||||
): Promise<FilterResult<Website[]>> {
|
): Promise<FilterResult<Website[]>> {
|
||||||
const { userId, teamId, query } = filters;
|
const { userId, teamId, query } = filters;
|
||||||
const mode = prisma.getQueryMode();
|
const mode = prisma.getQueryMode();
|
||||||
|
|
||||||
const where: Prisma.WebsiteWhereInput = {
|
const where: Prisma.WebsiteWhereInput = {
|
||||||
|
...criteria.where,
|
||||||
AND: [
|
AND: [
|
||||||
{
|
{
|
||||||
OR: [
|
OR: [
|
||||||
|
@ -47,34 +55,29 @@ export async function getWebsites(
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
deletedAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [pageFilters, getParameters] = prisma.getPageFilters({
|
return prisma.pagedQuery('website', { where }, filters);
|
||||||
orderBy: 'name',
|
|
||||||
...filters,
|
|
||||||
});
|
|
||||||
|
|
||||||
const websites = await prisma.client.website.findMany({
|
|
||||||
where: {
|
|
||||||
...where,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
...pageFilters,
|
|
||||||
...(options?.include && { include: options.include }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await prisma.client.website.count({ where: { ...where, deletedAt: null } });
|
|
||||||
|
|
||||||
return { data: websites, count, ...getParameters };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWebsitesByUserId(
|
export async function getAllWebsites(userId: string) {
|
||||||
|
return prisma.client.website.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserWebsites(
|
||||||
userId: string,
|
userId: string,
|
||||||
filters?: WebsiteSearchFilter,
|
filters?: WebsiteSearchFilter,
|
||||||
): Promise<FilterResult<Website[]>> {
|
): Promise<FilterResult<Website[]>> {
|
||||||
return getWebsites(
|
return getWebsites(
|
||||||
{ userId, ...filters },
|
|
||||||
{
|
{
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
|
@ -84,19 +87,22 @@ export async function getWebsitesByUserId(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
orderBy: 'name',
|
||||||
|
...filters,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWebsitesByTeamId(
|
export async function getTeamWebsites(
|
||||||
teamId: string,
|
teamId: string,
|
||||||
filters?: WebsiteSearchFilter,
|
filters?: WebsiteSearchFilter,
|
||||||
): Promise<FilterResult<Website[]>> {
|
): Promise<FilterResult<Website[]>> {
|
||||||
return getWebsites(
|
return getWebsites(
|
||||||
{
|
{
|
||||||
teamId,
|
where: {
|
||||||
...filters,
|
teamId,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
include: {
|
include: {
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
|
@ -106,80 +112,10 @@ export async function getWebsitesByTeamId(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
filters,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserWebsites(
|
|
||||||
userId: string,
|
|
||||||
options?: { includeTeams: boolean },
|
|
||||||
): Promise<Website[]> {
|
|
||||||
const { rawQuery } = prisma;
|
|
||||||
|
|
||||||
if (options?.includeTeams) {
|
|
||||||
const websites = await rawQuery(
|
|
||||||
`
|
|
||||||
select
|
|
||||||
website_id as "id",
|
|
||||||
name,
|
|
||||||
domain,
|
|
||||||
share_id as "shareId",
|
|
||||||
reset_at as "resetAt",
|
|
||||||
user_id as "userId",
|
|
||||||
created_at as "createdAt",
|
|
||||||
updated_at as "updatedAt",
|
|
||||||
deleted_at as "deletedAt",
|
|
||||||
null as "teamId",
|
|
||||||
null as "teamName"
|
|
||||||
from website
|
|
||||||
where user_id = {{userId::uuid}}
|
|
||||||
and deleted_at is null
|
|
||||||
union
|
|
||||||
select
|
|
||||||
w.website_id as "id",
|
|
||||||
w.name,
|
|
||||||
w.domain,
|
|
||||||
w.share_id as "shareId",
|
|
||||||
w.reset_at as "resetAt",
|
|
||||||
w.user_id as "userId",
|
|
||||||
w.created_at as "createdAt",
|
|
||||||
w.updated_at as "updatedAt",
|
|
||||||
w.deleted_at as "deletedAt",
|
|
||||||
t.team_id as "teamId",
|
|
||||||
t.name as "teamName"
|
|
||||||
from website w
|
|
||||||
inner join team_website tw
|
|
||||||
on tw.website_id = w.website_id
|
|
||||||
inner join team t
|
|
||||||
on t.team_id = tw.team_id
|
|
||||||
inner join team_user tu
|
|
||||||
on tu.team_id = tw.team_id
|
|
||||||
where tu.user_id = {{userId::uuid}}
|
|
||||||
and w.deleted_at is null
|
|
||||||
`,
|
|
||||||
{ userId },
|
|
||||||
);
|
|
||||||
|
|
||||||
return websites.reduce((arr, item) => {
|
|
||||||
if (!arr.find(({ id }) => id === item.id)) {
|
|
||||||
return arr.concat(item);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return prisma.client.website.findMany({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
name: 'asc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createWebsite(
|
export async function createWebsite(
|
||||||
data: Prisma.WebsiteCreateInput | Prisma.WebsiteUncheckedCreateInput,
|
data: Prisma.WebsiteCreateInput | Prisma.WebsiteUncheckedCreateInput,
|
||||||
): Promise<Website> {
|
): Promise<Website> {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user