import { Prisma } from "@prisma/client"; import type { ActionFunctionArgs } from "react-router"; import prisma from "../db.server"; import { authenticate } from "../shopify.server"; import { blindIndex } from "../services/crypto.server"; /** * Mandatory privacy webhooks. * customers/data_request → we hold booking contact details; logged for the merchant to fulfil. * customers/redact → anonymise that customer's bookings and waitlist entries. * shop/redact → delete all shop data (48h after uninstall). */ export const action = async ({ request }: ActionFunctionArgs) => { const { shop, topic, payload } = await authenticate.webhook(request); const p = payload as { customer?: { email?: string; phone?: string }; orders_to_redact?: number[] }; const s = await prisma.shop.findUnique({ where: { domain: shop } }); // Contact details are encrypted at rest; match them by keyed hash. const emailHash = blindIndex(p.customer?.email, "email"); const phoneHash = blindIndex(p.customer?.phone, "phone"); if (!s) return new Response(); if (topic === "CUSTOMERS_DATA_REQUEST") { const n = emailHash ? await prisma.booking.count({ where: { shopId: s.id, customerEmailHash: emailHash } }) : 0; console.info(`data_request for ${shop}: ${n} bookings hold this customer's contact details`); } if (topic === "CUSTOMERS_REDACT") { const orderIds = (p.orders_to_redact ?? []).map((id) => `gid://shopify/Order/${id}`); const where = { shopId: s.id, OR: [{ orderId: { in: orderIds } }, ...(emailHash ? [{ customerEmailHash: emailHash }] : [])], }; await prisma.booking.updateMany({ where, data: { customerName: null, customerEmail: null, customerPhone: null, address: Prisma.DbNull, customerEmailHash: null, customerPhoneHash: null }, }); if (emailHash || phoneHash) await prisma.waitlistEntry.deleteMany({ where: { shopId: s.id, OR: [...(emailHash ? [{ emailHash }] : []), ...(phoneHash ? [{ phoneHash }] : [])] } }); } if (topic === "SHOP_REDACT") { await prisma.shop.delete({ where: { id: s.id } }); await prisma.session.deleteMany({ where: { shop } }); } return new Response(); };