Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function SessionModal({ websiteId, ...props }: SessionModalProps) {
<Dialog variant="sheet" className="rounded-lg">
{({ close }) => (
<Column padding="10">
<SessionProfile websiteId={websiteId} sessionId={session} showReplays={!isSharePage} onClose={() => close()} />
<SessionProfile websiteId={websiteId} sessionId={session} showReplays={!isSharePage} allowDelete={!isSharePage} onClose={() => close()} />
</Column>
)}
</Dialog>
Expand Down
53 changes: 47 additions & 6 deletions src/app/(main)/websites/[websiteId]/sessions/SessionProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use client';
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
import { Avatar } from '@/components/common/Avatar';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useWebsiteSessionQuery } from '@/components/hooks';
import { useDeleteQuery, useMessages, useWebsiteSessionQuery } from '@/components/hooks';
import { Trash } from '@/components/icons';
import { DialogButton } from '@/components/input/DialogButton';
import {
Button,
Column,
Expand All @@ -24,15 +27,33 @@ export function SessionProfile({
websiteId,
sessionId,
showReplays = true,
allowDelete = true,
onClose,
onDelete,
}: {
websiteId: string;
sessionId: string;
showReplays?: boolean;
allowDelete?: boolean;
onClose?: () => void;
onDelete?: () => void;
}) {
const { data, isLoading, error } = useWebsiteSessionQuery(websiteId, sessionId);
const { t, labels } = useMessages();
const { t, labels, messages } = useMessages();
const { mutateAsync, isPending, error: deleteError, touch } = useDeleteQuery(
`/websites/${websiteId}/sessions/${sessionId}`,
);

const handleDelete = async (close: () => void) => {
await mutateAsync(null, {
onSuccess: () => {
touch('sessions');
close();
onDelete?.();
onClose?.();
},
});
};

return (
<LoadingPanel
Expand All @@ -44,15 +65,35 @@ export function SessionProfile({
>
{data && (
<Column gap>
{onClose && (
<Row justifyContent="flex-end">
<Row justifyContent="flex-end" gap>
{allowDelete && (
<DialogButton
icon={<Trash />}
variant="quiet"
title={t(labels.confirm)}
width="400px"
>
{({ close }) => (
<ConfirmationForm
message={t(messages.confirmDelete, { target: t(labels.session) })}
isLoading={isPending}
error={deleteError}
onConfirm={handleDelete.bind(null, close)}
onClose={close}
buttonLabel={t(labels.delete)}
buttonVariant="danger"
/>
)}
</DialogButton>
)}
{onClose && (
<Button onPress={onClose} variant="quiet">
<Icon>
<X />
</Icon>
</Button>
</Row>
)}
)}
</Row>
<Column gap="6">
<Row justifyContent="center" alignItems="center" gap="6">
<Avatar seed={data?.id} size={128} />
Expand Down
21 changes: 13 additions & 8 deletions src/app/(main)/websites/[websiteId]/sessions/[sessionId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import type { Metadata } from 'next';
'use client';
import { SessionProfile } from '@/app/(main)/websites/[websiteId]/sessions/SessionProfile';
import { useRouter } from 'next/navigation';
import { use } from 'react';

export default async function ({
export default function ({
params,
}: {
params: Promise<{ websiteId: string; sessionId: string }>;
}) {
const { websiteId, sessionId } = await params;
const { websiteId, sessionId } = use(params);
const router = useRouter();

return <SessionProfile websiteId={websiteId} sessionId={sessionId} />;
return (
<SessionProfile
websiteId={websiteId}
sessionId={sessionId}
onDelete={() => router.push(`/websites/${websiteId}/sessions`)}
/>
);
}

export const metadata: Metadata = {
title: 'Session',
};
36 changes: 34 additions & 2 deletions src/app/api/websites/[websiteId]/sessions/[sessionId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { canViewWebsite } from '@/permissions';
import { badRequest, json, notFound, ok, unauthorized } from '@/lib/response';
import { canDeleteWebsite, canViewWebsite } from '@/permissions';
import { deleteSession, findSession } from '@/queries/prisma';
import { getWebsiteSession } from '@/queries/sql';

export async function GET(
Expand All @@ -23,3 +24,34 @@ export async function GET(

return json(data);
}

export async function DELETE(
request: Request,
{ params }: { params: Promise<{ websiteId: string; sessionId: string }> },
) {
const { auth, error } = await parseRequest(request);

if (error) {
return error();
}

const { websiteId, sessionId } = await params;

if (!(await canDeleteWebsite(auth, websiteId))) {
return unauthorized();
}

if (process.env.CLICKHOUSE_URL) {
return badRequest({ message: 'Deleting individual sessions is not supported with ClickHouse.' });
}
Comment thread
cabaucom376 marked this conversation as resolved.

const session = await findSession(websiteId, sessionId);

if (!session) {
return notFound();
}

await deleteSession(websiteId, sessionId);

return ok();
}
1 change: 1 addition & 0 deletions src/queries/prisma/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './link';
export * from './pixel';
export * from './report';
export * from './segment';
export * from './session';
export * from './sessionReplay';
export * from './share';
export * from './team';
Expand Down
60 changes: 60 additions & 0 deletions src/queries/prisma/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import prisma from '@/lib/prisma';

export async function findSession(websiteId: string, sessionId: string) {
return prisma.client.session.findFirst({
where: { id: sessionId, websiteId },
});
}

export async function deleteSession(websiteId: string, sessionId: string) {
const { transaction } = prisma;

return transaction(
async tx => {
const replays = await tx.sessionReplay.findMany({
where: { websiteId, sessionId },
select: { visitId: true },
});
const visitIds = [...new Set(replays.map(r => r.visitId))];

if (visitIds.length > 0) {
await tx.sessionReplaySaved.deleteMany({
where: { websiteId, visitId: { in: visitIds } },
});
}

await tx.sessionReplay.deleteMany({
where: { websiteId, sessionId },
});

await tx.revenue.deleteMany({
where: { websiteId, sessionId },
});

const events = await tx.websiteEvent.findMany({
where: { websiteId, sessionId },
select: { id: true },
});
const eventIds = events.map(e => e.id);

if (eventIds.length > 0) {
await tx.eventData.deleteMany({
where: { websiteId, websiteEventId: { in: eventIds } },
});
}

await tx.websiteEvent.deleteMany({
where: { websiteId, sessionId },
});

await tx.sessionData.deleteMany({
where: { websiteId, sessionId },
});

return tx.session.deleteMany({
where: { id: sessionId, websiteId },
});
},
{ timeout: 30000 },
);
}