feat(search): full-platform search overhaul + view tracking + notes bucket
Service rewrite covers 14 entity buckets (clients, residential clients, yachts, companies, interests, residential interests, berths, invoices, expenses, documents, files, reminders, brochures, tags, notes, navigation) with prefix tsquery + trigram fallback, phone-digit normalization, and JOINs to client_contacts for email matching. New `notes` bucket searches across the four note tables (client, interest, yacht, company) via UNION + parent-entity label resolution (berth mooring for interests, name for yachts/companies). Renders at the bottom of the dropdown so broad-content matches don't crowd entity-specific hits — per the user's "low-noise" preference. Recently-viewed tracking persists last 20 entity views per user in Redis sorted set; CommandSearch surfaces them as the dropdown's default state and applies affinity ranking when the user types. ID-resolve endpoint accepts pasted UUIDs (or invoice numbers like `INV-2025-001`) and routes the rep straight to the entity, skipping the normal search bucket. Audit search service gains `entityIds[]` array filter for the new loadClientActivityAggregated() path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* `audit_logs.search_text`.
|
||||
*/
|
||||
|
||||
import { and, desc, eq, gte, lte, sql, type SQL } from 'drizzle-orm';
|
||||
import { and, desc, eq, gte, inArray, lte, sql, type SQL } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { auditLogs, type AuditLog } from '@/lib/db/schema/system';
|
||||
@@ -22,6 +22,10 @@ export interface AuditSearchOptions {
|
||||
entityType?: string;
|
||||
/** Filter by exact entity id (e.g. paste a uuid into search). */
|
||||
entityId?: string;
|
||||
/** Filter by an explicit list of entity ids (e.g. aggregated activity
|
||||
* for a client across all their interests). Overrides `entityId`
|
||||
* when both are supplied. Empty array short-circuits to zero rows. */
|
||||
entityIds?: string[];
|
||||
/** Filter by severity ('info' | 'warning' | 'error' | 'critical'). */
|
||||
severity?: string;
|
||||
/** Filter by source ('user' | 'system' | 'auth' | 'webhook' | 'cron' | 'job'). */
|
||||
@@ -45,7 +49,15 @@ export async function searchAuditLogs(options: AuditSearchOptions = {}): Promise
|
||||
if (options.userId) conds.push(eq(auditLogs.userId, options.userId));
|
||||
if (options.action) conds.push(eq(auditLogs.action, options.action));
|
||||
if (options.entityType) conds.push(eq(auditLogs.entityType, options.entityType));
|
||||
if (options.entityId) conds.push(eq(auditLogs.entityId, options.entityId));
|
||||
if (options.entityIds) {
|
||||
if (options.entityIds.length === 0) {
|
||||
// Short-circuit: caller passed an empty list → no possible match.
|
||||
return { rows: [], nextCursor: null };
|
||||
}
|
||||
conds.push(inArray(auditLogs.entityId, options.entityIds));
|
||||
} else if (options.entityId) {
|
||||
conds.push(eq(auditLogs.entityId, options.entityId));
|
||||
}
|
||||
if (options.severity) conds.push(eq(auditLogs.severity, options.severity));
|
||||
if (options.source) conds.push(eq(auditLogs.source, options.source));
|
||||
if (options.from) conds.push(gte(auditLogs.createdAt, options.from));
|
||||
|
||||
Reference in New Issue
Block a user