- 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>
36 lines
1.2 KiB
TypeScript
36 lines
1.2 KiB
TypeScript
import { PrismaClient } from "@prisma/client";
|
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
import { piiExtension } from "./lib/pii.server";
|
|
|
|
declare global {
|
|
// eslint-disable-next-line no-var
|
|
var prismaGlobal: PrismaClient | undefined;
|
|
}
|
|
|
|
/**
|
|
* Rust-free Prisma client over node-postgres (no native engine binary needed,
|
|
* which also makes shared/VPS hosting simpler). Every client encrypts customer
|
|
* personal data at rest (lib/pii.server.ts). The extension keeps the field types,
|
|
* so the client is typed as a plain PrismaClient.
|
|
*/
|
|
export function createPrisma(url = process.env.DATABASE_URL): PrismaClient {
|
|
if (!url) throw new Error("DATABASE_URL is not set");
|
|
return new PrismaClient({ adapter: new PrismaPg({ connectionString: url }) }).$extends(piiExtension) as unknown as PrismaClient;
|
|
}
|
|
|
|
function instance(): PrismaClient {
|
|
if (!global.prismaGlobal) global.prismaGlobal = createPrisma();
|
|
return global.prismaGlobal;
|
|
}
|
|
|
|
/** Lazily created so importing this module never needs DATABASE_URL (tests, build). */
|
|
const prisma = new Proxy({} as PrismaClient, {
|
|
get(_t, prop) {
|
|
const db = instance();
|
|
const v = Reflect.get(db, prop);
|
|
return typeof v === "function" ? v.bind(db) : v;
|
|
},
|
|
});
|
|
|
|
export default prisma;
|