Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | 18x 13x 5x 3x 2x 8x | import { desc, eq, inArray } from "drizzle-orm";
import type { DrizzleD1Database } from "drizzle-orm/d1";
import * as schema from "../../db/schema";
import type { RrmSubmission } from "../../db/schema/rrm";
const SUBMISSIONS = schema.rrmSubmissions;
/**
* `rrm_submissions` reads and the DPDP-erasure half that belongs to this
* table (loose-ends review D3).
*
* No FK to `rrm_prospects` — same reasoning as `rrm_prospect_events` — but
* unlike the event log this table is NOT append-only: `scrubForProspect`
* exists precisely to mutate it, because a submission is where the realtor's
* own details AND the referred contact's name/phone/project live (see the
* column comments on `rrm_submissions` in `db/schema/rrm.ts`).
*/
export class RrmSubmissionsDal {
constructor(private db: DrizzleD1Database<typeof schema>) {}
/** One prospect's submissions, newest first. Powers the prospect detail card. */
async findByProspectId(prospectId: string): Promise<RrmSubmission[]> {
return await this.db
.select()
.from(SUBMISSIONS)
.where(eq(SUBMISSIONS.prospectId, prospectId))
.orderBy(desc(SUBMISSIONS.dateCreated));
}
/**
* Every submission for a page of prospect ids, in one query — the Sign-ups
* list's N+1 guard. Globally newest-first, so a caller building a
* per-prospect "latest submission" only needs to keep the FIRST row it
* sees for each `prospectId` while it walks the result once.
*
* Caller's responsibility to keep `prospectIds` well under D1's 100-bound-
* parameter ceiling — the Sign-ups route pages at 50 for exactly this
* reason (CLAUDE.md's D1 100-bind-limit note).
*/
async findByProspectIds(prospectIds: string[]): Promise<RrmSubmission[]> {
if (prospectIds.length === 0) return [];
return await this.db
.select()
.from(SUBMISSIONS)
.where(inArray(SUBMISSIONS.prospectId, prospectIds))
.orderBy(desc(SUBMISSIONS.dateCreated));
}
/**
* DPDP erasure, the submissions half (review D3). Nulls the realtor's own
* identifying fields AND the referred contact's structured details —
* `contactName` / `contactPhoneNorm` / `contactProject` — plus the free-text
* `contactNote`, which is where an unstructured phone number or name is
* just as likely to land. `phoneNorm` (the realtor's own, NOT NULL) and
* `consentVersion` (also NOT NULL, and not personal data) survive, same as
* `id` / `phone_norm` survive `RrmProspectsDal.erase()` — an identity
* anchor and the consent-basis record are not what erasure is for.
*
* Called by the `/erase` ROUTE (`routes/admin/rrm/prospects.routes.ts`),
* which orchestrates every store a DPDP erasure has to touch in one
* request — this DAL does not call itself.
*/
async scrubForProspect(prospectId: string): Promise<void> {
await this.db
.update(SUBMISSIONS)
.set({
name: null,
firmName: null,
contactName: null,
contactPhoneNorm: null,
contactProject: null,
ipHash: null,
userAgent: null,
contactNote: null,
})
.where(eq(SUBMISSIONS.prospectId, prospectId));
}
}
|