← back to Grant

lib/audit.ts

36 lines

import { query } from './db';

/**
 * Log an audit event to the audit_events table.
 *
 * @param eventType   - e.g. 'grant.created', 'login', 'org.updated'
 * @param entityType  - e.g. 'grant', 'user', 'organization'
 * @param entityId    - primary key of the affected entity (nullable)
 * @param orgId       - organization ID for multi-tenant scoping (nullable)
 * @param userId      - user who performed the action (nullable)
 * @param metadata    - arbitrary JSON payload with extra context
 * @param ipAddress   - client IP if available
 */
export async function auditLog(
  eventType: string,
  entityType: string,
  entityId: string | null,
  orgId?: string | null,
  userId?: string | null,
  metadata?: Record<string, unknown>,
  ipAddress?: string,
): Promise<void> {
  const metaJson = metadata ? JSON.stringify(metadata) : null;

  try {
    await query(
      `INSERT INTO audit_events (event_type, entity_type, entity_id, org_id, user_id, metadata, ip_address)
       VALUES ($1, $2, $3, $4, $5, $6, $7)`,
      [eventType, entityType, entityId, orgId ?? null, userId ?? null, metaJson, ipAddress ?? null],
    );
  } catch (err) {
    // Audit failures should never crash the calling operation
    console.error('[audit] Failed to write audit event:', (err as Error).message);
  }
}