- Prisma extension seals booking name/email/phone/address and waitlist contacts with AES-256-GCM on write and opens them on read (incl. nested reads) - keyed-hash columns for exact email/phone lookups (privacy webhooks, booking search, waitlist de-duplication); migration 0002 - tests prove only ciphertext is stored; privacy/terms/SECURITY.md updated to match Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 lines
1.5 KiB
TypeScript
28 lines
1.5 KiB
TypeScript
import type { Prisma } from "@prisma/client";
|
|
import { blindIndex } from "../services/crypto.server";
|
|
|
|
/** Booking list filters shared by the list page and the CSV export. */
|
|
export function bookingWhere(shopId: string, sp: URLSearchParams, today: string): Prisma.BookingWhereInput {
|
|
const status = sp.get("status") ?? "upcoming";
|
|
const where: Prisma.BookingWhereInput = { shopId };
|
|
if (status === "review") where.OR = [{ status: "needs_review" }, { status: "confirmed", reviewReason: { not: null } }];
|
|
else if (status === "upcoming") Object.assign(where, { status: "confirmed", date: { gte: today } });
|
|
else if (status !== "all") where.status = status;
|
|
const method = sp.get("method");
|
|
if (method && method !== "all") where.method = method;
|
|
const loc = sp.get("location");
|
|
if (loc && loc !== "all") where.locationId = loc;
|
|
const from = sp.get("from");
|
|
const to = sp.get("to");
|
|
if (from || to) where.date = { ...(from ? { gte: from } : {}), ...(to ? { lte: to } : {}) };
|
|
const q = sp.get("q")?.trim();
|
|
// Customer details are encrypted at rest: match order numbers by text, emails and phones exactly (by keyed hash).
|
|
if (q) {
|
|
const or: Prisma.BookingWhereInput[] = [{ orderName: { contains: q, mode: "insensitive" } }];
|
|
if (q.includes("@")) or.push({ customerEmailHash: blindIndex(q, "email") });
|
|
else if (/^[\d\s()+.-]{7,}$/.test(q)) or.push({ customerPhoneHash: blindIndex(q, "phone") });
|
|
where.AND = [{ OR: or }];
|
|
}
|
|
return where;
|
|
}
|