From db2d8d3d5046c44303a38204fabe5432b527d5a6 Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Sat, 31 May 2025 19:41:45 -0700 Subject: [PATCH] Add user names to audit log displays (#58) Include the email in the detail view. --- src/actions/auditLogs.ts | 37 +++++++++++-------- .../(authenticated)/audit-logs/AuditLogs.tsx | 8 ++-- src/components/audit-logs/AuditLogCard.tsx | 8 ++-- src/components/audit-logs/AuditLogDetails.tsx | 8 ++-- src/components/audit-logs/AuditLogDrawer.tsx | 4 +- src/components/audit-logs/AuditLogEntry.tsx | 8 ++-- src/db/db.ts | 10 ++--- src/db/types.ts | 4 ++ 8 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/actions/auditLogs.ts b/src/actions/auditLogs.ts index a2e7510472..ebeac188da 100644 --- a/src/actions/auditLogs.ts +++ b/src/actions/auditLogs.ts @@ -5,7 +5,7 @@ import { eq, gte, and, desc } from 'drizzle-orm'; import { logger } from '@/lib/server'; import { type DatabaseOrTransaction, - type AuditLog, + type AuditLogWithUser, type CreateAuditLog, client as db, auditLogs, @@ -19,7 +19,7 @@ export const getAuditLogs = async ({ orgId?: string | null; limit?: number; timePeriod?: number; -}): Promise => { +}): Promise => { if (!orgId) { return []; } @@ -30,12 +30,14 @@ export const getAuditLogs = async ({ } if (timePeriod === undefined) { - return await db - .select() - .from(auditLogs) - .where(eq(auditLogs.orgId, orgId)) - .orderBy(desc(auditLogs.createdAt)) - .limit(limit); + return await db.query.auditLogs.findMany({ + where: eq(auditLogs.orgId, orgId), + with: { + user: true, + }, + orderBy: desc(auditLogs.createdAt), + limit: limit, + }); } else { if (isNaN(timePeriod) || timePeriod <= 0) { throw new Error('timePeriod must be a positive number'); @@ -44,14 +46,17 @@ export const getAuditLogs = async ({ const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - timePeriod); - return await db - .select() - .from(auditLogs) - .where( - and(eq(auditLogs.orgId, orgId), gte(auditLogs.createdAt, cutoff)), - ) - .orderBy(desc(auditLogs.createdAt)) - .limit(limit); + return await db.query.auditLogs.findMany({ + where: and( + eq(auditLogs.orgId, orgId), + gte(auditLogs.createdAt, cutoff), + ), + with: { + user: true, + }, + orderBy: desc(auditLogs.createdAt), + limit: limit, + }); } } catch (error) { logger.error({ diff --git a/src/app/(authenticated)/audit-logs/AuditLogs.tsx b/src/app/(authenticated)/audit-logs/AuditLogs.tsx index ab34709052..450abfda49 100644 --- a/src/app/(authenticated)/audit-logs/AuditLogs.tsx +++ b/src/app/(authenticated)/audit-logs/AuditLogs.tsx @@ -5,7 +5,7 @@ import { useAuth } from '@clerk/nextjs'; import { useQuery } from '@tanstack/react-query'; import { type TimePeriod, timePeriods } from '@/types'; -import type { AuditLog } from '@/db'; +import type { AuditLogWithUser } from '@/db'; import { getAuditLogs } from '@/actions/auditLogs'; import { Button, @@ -29,7 +29,7 @@ export const AuditLogs = () => { enabled: !!orgId, }); - const [selectedLog, setSelectedLog] = useState(null); + const [selectedLog, setSelectedLog] = useState(null); return ( <> @@ -61,11 +61,11 @@ export const AuditLogs = () => { ) : logs.length > 0 ? ( - logs.map((log: AuditLog) => ( + logs.map((log: AuditLogWithUser) => ( setSelectedLog(log)} + onClick={(log: AuditLogWithUser) => setSelectedLog(log)} /> )) ) : ( diff --git a/src/components/audit-logs/AuditLogCard.tsx b/src/components/audit-logs/AuditLogCard.tsx index f54900537c..b2c8905bee 100644 --- a/src/components/audit-logs/AuditLogCard.tsx +++ b/src/components/audit-logs/AuditLogCard.tsx @@ -7,7 +7,7 @@ import { useAuth } from '@clerk/nextjs'; import { useQuery } from '@tanstack/react-query'; import { ArrowRightIcon } from 'lucide-react'; -import type { AuditLog } from '@/db'; +import type { AuditLogWithUser } from '@/db'; import { getAuditLogs } from '@/actions/auditLogs'; import { Card, @@ -33,7 +33,7 @@ export function AuditLogCard() { enabled: !!orgId, }); - const [selectedLog, setSelectedLog] = useState(null); + const [selectedLog, setSelectedLog] = useState(null); const path = usePathname(); return ( @@ -70,11 +70,11 @@ export function AuditLogCard() { ) : logs.length > 0 ? ( - logs.map((log: AuditLog) => ( + logs.map((log: AuditLogWithUser) => ( setSelectedLog(log)} + onClick={(log: AuditLogWithUser) => setSelectedLog(log)} /> )) ) : ( diff --git a/src/components/audit-logs/AuditLogDetails.tsx b/src/components/audit-logs/AuditLogDetails.tsx index 9f407f7eab..f6acaf4d5f 100644 --- a/src/components/audit-logs/AuditLogDetails.tsx +++ b/src/components/audit-logs/AuditLogDetails.tsx @@ -1,9 +1,9 @@ import Link from 'next/link'; import { ArrowRight, Calendar, Clock, User } from 'lucide-react'; -import type { AuditLog } from '@/db'; +import type { AuditLogWithUser } from '@/db'; -export const AuditLogDetails = ({ log }: { log: AuditLog }) => ( +export const AuditLogDetails = ({ log }: { log: AuditLogWithUser }) => (
@@ -14,7 +14,9 @@ export const AuditLogDetails = ({ log }: { log: AuditLog }) => (
- {log.userId} + + {log.user.name} ({log.user.email}) +
{log.targetId && ( void; }; diff --git a/src/components/audit-logs/AuditLogEntry.tsx b/src/components/audit-logs/AuditLogEntry.tsx index d569d85b5b..46ff8edb81 100644 --- a/src/components/audit-logs/AuditLogEntry.tsx +++ b/src/components/audit-logs/AuditLogEntry.tsx @@ -1,11 +1,11 @@ import { formatDistance } from 'date-fns'; import { Settings, Sliders, Users } from 'lucide-react'; -import { type AuditLog, AuditLogTargetType } from '@/db'; +import { type AuditLogWithUser, AuditLogTargetType } from '@/db'; type AuditLogEntryProps = { - log: AuditLog; - onClick: (log: AuditLog) => void; + log: AuditLogWithUser; + onClick: (log: AuditLogWithUser) => void; }; export const AuditLogEntry = ({ log, onClick }: AuditLogEntryProps) => ( @@ -18,7 +18,7 @@ export const AuditLogEntry = ({ log, onClick }: AuditLogEntryProps) => (

{log.description}

-

{log.userId}

+

{log.user.name}

{formatDistance(log.createdAt, new Date(), { addSuffix: true })} diff --git a/src/db/db.ts b/src/db/db.ts index ae77ab84c5..dde0bd73d8 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -2,10 +2,13 @@ import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import { Env } from '@/lib/server'; +import * as schema from './schema'; const pgClient = postgres(Env.DATABASE_URL, { prepare: false }); -let testDb: ReturnType | undefined = undefined; +const client = drizzle({ client: pgClient, schema }); + +let testDb: typeof client | undefined = undefined; if (process.env.NODE_ENV === 'test') { if ( @@ -15,12 +18,9 @@ if (process.env.NODE_ENV === 'test') { throw new Error('DATABASE_URL is not a test database'); } - testDb = drizzle({ client: pgClient }); + testDb = client; } -const client = - process.env.NODE_ENV === 'test' ? testDb! : drizzle({ client: pgClient }); - const disconnect = async () => { await pgClient.end(); }; diff --git a/src/db/types.ts b/src/db/types.ts index bded50e8f0..0d627458e5 100644 --- a/src/db/types.ts +++ b/src/db/types.ts @@ -36,3 +36,7 @@ export type CreateOrgSettings = Omit< export type AuditLog = typeof auditLogs.$inferSelect; export type CreateAuditLog = Omit; + +export type AuditLogWithUser = AuditLog & { + user: User; +};