feat(audit): implement page view logging for membership confirmation and analysis results

This commit is contained in:
Danel Kungla
2025-08-14 09:37:03 +03:00
parent 536f915c69
commit bbb5e83ed9
3 changed files with 75 additions and 28 deletions

View File

@@ -5,6 +5,10 @@ import pathsConfig from '@/config/paths.config';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
import {
PAGE_VIEW_ACTION,
createPageViewLog,
} from '~/lib/services/audit/pageView.service';
import MembershipConfirmationNotification from './_components/membership-confirmation-notification'; import MembershipConfirmationNotification from './_components/membership-confirmation-notification';
@@ -18,6 +22,10 @@ async function MembershipConfirmation() {
if (!user?.id) { if (!user?.id) {
redirect(pathsConfig.app.home); redirect(pathsConfig.app.home);
} }
await createPageViewLog({
accountId: user.id,
action: PAGE_VIEW_ACTION.REGISTRATION_SUCCESS,
});
return <MembershipConfirmationNotification userId={user.id} />; return <MembershipConfirmationNotification userId={user.id} />;
} }

View File

@@ -1,5 +1,7 @@
import Link from 'next/link'; import Link from 'next/link';
import { redirect } from 'next/navigation';
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
import { withI18n } from '@/lib/i18n/with-i18n'; import { withI18n } from '@/lib/i18n/with-i18n';
@@ -7,14 +9,16 @@ import { Trans } from '@kit/ui/makerkit/trans';
import { PageBody } from '@kit/ui/page'; import { PageBody } from '@kit/ui/page';
import { Button } from '@kit/ui/shadcn/button'; import { Button } from '@kit/ui/shadcn/button';
import pathsConfig from '~/config/paths.config';
import { getAnalysisElements } from '~/lib/services/analysis-element.service';
import {
PAGE_VIEW_ACTION,
createPageViewLog,
} from '~/lib/services/audit/pageView.service';
import { getAnalysisOrders } from '~/lib/services/order.service';
import { loadUserAnalysis } from '../../_lib/server/load-user-analysis'; import { loadUserAnalysis } from '../../_lib/server/load-user-analysis';
import Analysis from './_components/analysis'; import Analysis from './_components/analysis';
import pathsConfig from '~/config/paths.config';
import { redirect } from 'next/navigation';
import { getAnalysisOrders } from '~/lib/services/order.service';
import { getAnalysisElements } from '~/lib/services/analysis-element.service';
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
import { createPageViewLog } from '~/lib/services/audit/pageView.service';
export const generateMetadata = async () => { export const generateMetadata = async () => {
const i18n = await createI18nServerInstance(); const i18n = await createI18nServerInstance();
@@ -26,13 +30,15 @@ export const generateMetadata = async () => {
}; };
async function AnalysisResultsPage() { async function AnalysisResultsPage() {
const account = await loadCurrentUserAccount() const account = await loadCurrentUserAccount();
if (!account) { if (!account) {
throw new Error('Account not found'); throw new Error('Account not found');
} }
const analysisResponses = await loadUserAnalysis(); const analysisResponses = await loadUserAnalysis();
const analysisResponseElements = analysisResponses?.flatMap(({ elements }) => elements); const analysisResponseElements = analysisResponses?.flatMap(
({ elements }) => elements,
);
const analysisOrders = await getAnalysisOrders().catch(() => null); const analysisOrders = await getAnalysisOrders().catch(() => null);
@@ -42,29 +48,46 @@ async function AnalysisResultsPage() {
await createPageViewLog({ await createPageViewLog({
accountId: account.id, accountId: account.id,
action: 'VIEW_ANALYSIS_RESULTS', action: PAGE_VIEW_ACTION.VIEW_ANALYSIS_RESULTS,
}); });
const analysisElementIds = [ const analysisElementIds = [
...new Set(analysisOrders?.flatMap((order) => order.analysis_element_ids).filter(Boolean) as number[]), ...new Set(
analysisOrders
?.flatMap((order) => order.analysis_element_ids)
.filter(Boolean) as number[],
),
]; ];
const analysisElements = await getAnalysisElements({ ids: analysisElementIds }); const analysisElements = await getAnalysisElements({
const analysisElementsWithResults = analysisResponseElements ids: analysisElementIds,
?.sort((a, b) => { });
if (!a.response_time || !b.response_time) { const analysisElementsWithResults =
return 0; analysisResponseElements
} ?.sort((a, b) => {
return new Date(b.response_time).getTime() - new Date(a.response_time).getTime(); if (!a.response_time || !b.response_time) {
}) return 0;
.map((results) => ({ results })) ?? []; }
const analysisElementsWithoutResults = analysisElements return (
.filter((element) => !analysisElementsWithResults?.some(({ results }) => results.analysis_element_original_id === element.analysis_id_original)); new Date(b.response_time).getTime() -
new Date(a.response_time).getTime()
);
})
.map((results) => ({ results })) ?? [];
const analysisElementsWithoutResults = analysisElements.filter(
(element) =>
!analysisElementsWithResults?.some(
({ results }) =>
results.analysis_element_original_id === element.analysis_id_original,
),
);
const hasNoAnalysisElements = analysisElementsWithResults.length === 0 && analysisElementsWithoutResults.length === 0; const hasNoAnalysisElements =
analysisElementsWithResults.length === 0 &&
analysisElementsWithoutResults.length === 0;
return ( return (
<PageBody> <PageBody>
<div className="mt-8 flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0"> <div className="mt-8 flex flex-col justify-between gap-4 sm:flex-row sm:items-center sm:gap-0">
<div> <div>
<h4> <h4>
<Trans i18nKey="analysis-results:pageTitle" /> <Trans i18nKey="analysis-results:pageTitle" />
@@ -85,16 +108,27 @@ async function AnalysisResultsPage() {
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{analysisElementsWithResults.map(({ results }) => { {analysisElementsWithResults.map(({ results }) => {
const analysisElement = analysisElements.find((element) => element.analysis_id_original === results.analysis_element_original_id); const analysisElement = analysisElements.find(
(element) =>
element.analysis_id_original ===
results.analysis_element_original_id,
);
if (!analysisElement) { if (!analysisElement) {
return null; return null;
} }
return ( return (
<Analysis key={results.id} analysisElement={analysisElement} results={results} /> <Analysis
key={results.id}
analysisElement={analysisElement}
results={results}
/>
); );
})} })}
{analysisElementsWithoutResults.map((element) => ( {analysisElementsWithoutResults.map((element) => (
<Analysis key={element.analysis_id_original} analysisElement={element} /> <Analysis
key={element.analysis_id_original}
analysisElement={element}
/>
))} ))}
{hasNoAnalysisElements && ( {hasNoAnalysisElements && (
<div className="text-muted-foreground text-sm"> <div className="text-muted-foreground text-sm">

View File

@@ -1,11 +1,16 @@
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
export enum PAGE_VIEW_ACTION {
VIEW_ANALYSIS_RESULTS = 'VIEW_ANALYSIS_RESULTS',
REGISTRATION_SUCCESS = 'REGISTRATION_SUCCESS',
}
export const createPageViewLog = async ({ export const createPageViewLog = async ({
accountId, accountId,
action, action,
}: { }: {
accountId: string; accountId: string;
action: 'VIEW_ANALYSIS_RESULTS'; action: PAGE_VIEW_ACTION;
}) => { }) => {
try { try {
const supabase = getSupabaseServerClient(); const supabase = getSupabaseServerClient();
@@ -32,4 +37,4 @@ export const createPageViewLog = async ({
} catch (error) { } catch (error) {
console.error('Failed to insert page view log', error); console.error('Failed to insert page view log', error);
} }
} };