From 83fff1ffe7576f7cb3c28bb50e864c37de895f9b Mon Sep 17 00:00:00 2001 From: k4rli Date: Mon, 11 Aug 2025 09:21:13 +0300 Subject: [PATCH] feat(MED-105): create audit entry on analysis results view --- .../(dashboard)/analysis-results/page.tsx | 12 +++++++ lib/services/audit/pageView.service.ts | 35 +++++++++++++++++++ packages/supabase/src/database.types.ts | 24 +++++++++++++ .../20250811065135_audit_page_views.sql | 24 +++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 lib/services/audit/pageView.service.ts create mode 100644 supabase/migrations/20250811065135_audit_page_views.sql diff --git a/app/home/(user)/(dashboard)/analysis-results/page.tsx b/app/home/(user)/(dashboard)/analysis-results/page.tsx index 4060560..7b8a97e 100644 --- a/app/home/(user)/(dashboard)/analysis-results/page.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/page.tsx @@ -15,6 +15,8 @@ import { redirect } from 'next/navigation'; import { getOrders } from '~/lib/services/order.service'; import { AnalysisElement, getAnalysisElements } from '~/lib/services/analysis-element.service'; import type { UserAnalysisElement } from '@kit/accounts/types/accounts'; +import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; +import { createPageViewLog } from '~/lib/services/audit/pageView.service'; export const generateMetadata = async () => { const i18n = await createI18nServerInstance(); @@ -26,6 +28,11 @@ export const generateMetadata = async () => { }; async function AnalysisResultsPage() { + const account = await loadCurrentUserAccount() + if (!account) { + throw new Error('Account not found'); + } + const analysisList = await loadUserAnalysis(); const orders = await getOrders().catch(() => null); @@ -35,6 +42,11 @@ async function AnalysisResultsPage() { redirect(pathsConfig.auth.signIn); } + await createPageViewLog({ + accountId: account.id, + action: 'VIEW_ANALYSIS_RESULTS', + }); + const analysisElementIds = [ ...new Set(orders?.flatMap((order) => order.analysis_element_ids).filter(Boolean) as number[]), ]; diff --git a/lib/services/audit/pageView.service.ts b/lib/services/audit/pageView.service.ts new file mode 100644 index 0000000..719db6d --- /dev/null +++ b/lib/services/audit/pageView.service.ts @@ -0,0 +1,35 @@ +import { getSupabaseServerClient } from '@kit/supabase/server-client'; + +export const createPageViewLog = async ({ + accountId, + action, +}: { + accountId: string; + action: 'VIEW_ANALYSIS_RESULTS'; +}) => { + try { + const supabase = getSupabaseServerClient(); + + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + + if (userError || !user) { + console.error('No authenticated user found; skipping audit insert'); + return; + } + + await supabase + .schema('audit') + .from('page_views') + .insert({ + account_id: accountId, + action, + changed_by: user.id, + }) + .throwOnError(); + } catch (error) { + console.error('Failed to insert page view log', error); + } +} diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 94e7d39..f7316cc 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -81,6 +81,30 @@ export type Database = { } Relationships: [] } + page_views: { + Row: { + account_id: string + action: string + changed_by: string + created_at: string + id: number + } + Insert: { + account_id: string + action: string + changed_by: string + created_at?: string + id?: number + } + Update: { + account_id?: string + action: string + changed_by?: string + created_at?: string + id?: number + } + Relationships: [] + } request_entries: { Row: { comment: string | null diff --git a/supabase/migrations/20250811065135_audit_page_views.sql b/supabase/migrations/20250811065135_audit_page_views.sql new file mode 100644 index 0000000..ceab355 --- /dev/null +++ b/supabase/migrations/20250811065135_audit_page_views.sql @@ -0,0 +1,24 @@ +create table "audit"."page_views" ( + "id" bigint generated by default as identity not null, + "account_id" text not null, + "action" text not null, + "created_at" timestamp with time zone not null default now(), + "changed_by" uuid not null +); + +grant usage on schema audit to authenticated; +grant select, insert, update, delete on table audit.page_views to authenticated; + +alter table "audit"."page_views" enable row level security; + +create policy "insert_own" +on "audit"."page_views" +as permissive +for insert +to authenticated +with check (auth.uid() = changed_by); + +create policy "service_role_select" on "audit"."page_views" for select to service_role using (true); +create policy "service_role_insert" on "audit"."page_views" for insert to service_role with check (true); +create policy "service_role_update" on "audit"."page_views" for update to service_role using (true); +create policy "service_role_delete" on "audit"."page_views" for delete to service_role using (true);