import { index, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import type { Patient } from "../../types/patient.js"; import { organization, user } from "./auth.js"; export type WalletShareStatus = | "pending" | "approved" | "denied" | "expired"; export type WalletShareMode = "permanent" | "temporary"; // One row per "import from a patient app" request. A clinician enters a wallet // number; we mint a per-request ephemeral X25519 keypair (the phone seals the // record bundle to its public key) and relay a `share:request` to the device // over the /wallet Socket.io namespace. The patient approves on their phone; the // sealed bundle comes back, we decrypt it with `ephemeralPrivEnc` and hand the // clinic a draft Patient to review. Nothing here is the record itself — only the // transient handshake + the decrypted draft cached until the clinic commits it. export const walletShareRequests = pgTable( "wallet_share_requests", { id: uuid("id").primaryKey().defaultRandom(), organizationId: text("organization_id") .notNull() .references(() => organization.id, { onDelete: "cascade" }), requestedBy: text("requested_by") .notNull() .references(() => user.id, { onDelete: "cascade" }), // Null for QR "scan to connect" pairing requests — the wallet number is // bound when the authenticated device responds. Set up-front for the // type-the-number push flow. walletNumber: text("wallet_number"), ephemeralPubKey: text("ephemeral_pub_key").notNull(), // Encrypted (lib/crypto.ts) hex of the ephemeral X25519 private key. ephemeralPrivEnc: text("ephemeral_priv_enc").notNull(), status: text("status").$type().notNull().default("pending"), shareMode: text("share_mode").$type().notNull().default("permanent"), // For temporary shares: when the imported record should be auto-deleted. shareExpiresAt: timestamp("share_expires_at"), // The decrypted, verified draft record, cached between approval and commit. draft: jsonb("draft").$type(), // Set once the clinic commits the draft — the imported patient's file number, // so a later patient "revoke" from the app can delete exactly that record. committedFileNumber: text("committed_file_number"), createdAt: timestamp("created_at").defaultNow().notNull(), resolvedAt: timestamp("resolved_at"), }, (t) => [ index("wallet_share_org_idx").on(t.organizationId), index("wallet_share_wallet_idx").on(t.walletNumber), ], );