feat(schema): berths.archived_at + clients.source_inquiry_id + email_bounces
Step 3 schema additions per PRE-DEPLOY-PLAN § 1.4. berths.archived_at (+ archived_by, archive_reason) — soft-delete column so retired moorings can be hidden from the public feed and admin lists without losing historical interest joins. Partial index `idx_berths_active` on (port_id) WHERE archived_at IS NULL keeps the active-only list path fast. Already wired: - /api/public/berths and /api/public/berths/[mooringNumber] now filter out archived rows. - berths.service.listBerths defaults to active-only with an ?includeArchived=true escape hatch for the archive bin. clients.source_inquiry_id — text column with ON DELETE SET NULL FK to website_submissions(id). Preserves the linkage from a website inquiry to the client that came out of the "Convert to client" triage flow (P-4.5). Drives the conversion-funnel-by-source chart (Step 6). The Drizzle column ships without `.references()` to avoid the cross-file circular import; the FK lives in the migration SQL. email_bounces table — bounce-monitoring storage. The DSN poller worker (forthcoming, depends on this table existing) writes one row per parsed bounce; consumers join via (original_send_type, original_send_id). Three secondary indexes cover the expected access patterns (port + recent bounces; lookup by bounced address; lookup by original send). Schema additions plus the migration SQL are ready for `pnpm db:push` (or the migration runner once its journal is backfilled — separate concern, journal currently stops at 0042 despite migrations through 0065 existing on disk). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
46
src/lib/db/schema/email-bounces.ts
Normal file
46
src/lib/db/schema/email-bounces.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { pgTable, text, timestamp, index } from 'drizzle-orm/pg-core';
|
||||
import { ports } from './ports';
|
||||
|
||||
/**
|
||||
* Bounce-monitoring storage. The IMAP poller writes one row per parsed
|
||||
* DSN (Delivery Status Notification) it finds in a monitored sender's
|
||||
* inbox. Consumers (admin/bounces page, notifications worker, UI badges
|
||||
* on document_sends rows) join in via `originalSendType` /
|
||||
* `originalSendId`.
|
||||
*/
|
||||
export const emailBounces = pgTable(
|
||||
'email_bounces',
|
||||
{
|
||||
id: text('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
portId: text('port_id')
|
||||
.notNull()
|
||||
.references(() => ports.id, { onDelete: 'cascade' }),
|
||||
/** The mailbox we polled to find this bounce. */
|
||||
mailboxAddress: text('mailbox_address').notNull(),
|
||||
/** The address that bounced (the original recipient). */
|
||||
bouncedAddress: text('bounced_address').notNull(),
|
||||
/** One of `document_send` / `notification` / `email_thread` / `null`. */
|
||||
originalSendType: text('original_send_type'),
|
||||
/** The id of the original send row, when resolvable. */
|
||||
originalSendId: text('original_send_id'),
|
||||
/** RFC 3464 DSN fields. Null when the upstream provider uses a
|
||||
* non-RFC bounce format (some providers send HTML-only bounces). */
|
||||
dsnAction: text('dsn_action'),
|
||||
dsnStatus: text('dsn_status'),
|
||||
dsnDiagnostic: text('dsn_diagnostic'),
|
||||
receivedAt: timestamp('received_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
/** Full raw message for forensics. Trimmed to ~32KB before insert. */
|
||||
rawMessage: text('raw_message'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_email_bounces_port_received').on(table.portId, table.receivedAt),
|
||||
index('idx_email_bounces_bounced_address').on(table.bouncedAddress),
|
||||
index('idx_email_bounces_original_send').on(table.originalSendType, table.originalSendId),
|
||||
],
|
||||
);
|
||||
|
||||
export type EmailBounce = typeof emailBounces.$inferSelect;
|
||||
export type NewEmailBounce = typeof emailBounces.$inferInsert;
|
||||
Reference in New Issue
Block a user