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:
66
src/lib/db/migrations/0065_predeploy_schema.sql
Normal file
66
src/lib/db/migrations/0065_predeploy_schema.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
-- 0065_predeploy_schema.sql
|
||||
-- Pre-deploy schema additions per PRE-DEPLOY-PLAN § 1.4:
|
||||
-- 1. berths.archived_at — soft-delete column + partial index, filter for the
|
||||
-- public berth feed so retired berths stop showing up on the marketing site.
|
||||
-- 2. clients.source_inquiry_id — preserves the linkage from a website inquiry
|
||||
-- to the client that came out of the "Convert to client" triage step
|
||||
-- (P-4.5). Drives the conversion-funnel-by-source chart.
|
||||
-- 3. email_bounces — bounce-monitoring storage; the IMAP poller writes here
|
||||
-- and document_sends / notifications / email_threads consumers can join
|
||||
-- this in to surface "your message bounced" indicators.
|
||||
|
||||
-- ─── berths.archived_at ─────────────────────────────────────────────────────
|
||||
ALTER TABLE berths
|
||||
ADD COLUMN IF NOT EXISTS archived_at timestamptz;
|
||||
|
||||
ALTER TABLE berths
|
||||
ADD COLUMN IF NOT EXISTS archived_by text;
|
||||
|
||||
ALTER TABLE berths
|
||||
ADD COLUMN IF NOT EXISTS archive_reason text;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_berths_active
|
||||
ON berths (port_id)
|
||||
WHERE archived_at IS NULL;
|
||||
|
||||
-- ─── clients.source_inquiry_id ──────────────────────────────────────────────
|
||||
ALTER TABLE clients
|
||||
ADD COLUMN IF NOT EXISTS source_inquiry_id text REFERENCES website_submissions(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_clients_source_inquiry
|
||||
ON clients (source_inquiry_id)
|
||||
WHERE source_inquiry_id IS NOT NULL;
|
||||
|
||||
-- ─── email_bounces ──────────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS email_bounces (
|
||||
id text PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||||
port_id text NOT NULL REFERENCES ports(id) ON DELETE CASCADE,
|
||||
-- The mailbox we polled to find this bounce (e.g. 'noreply@portnimara.com').
|
||||
mailbox_address text NOT NULL,
|
||||
-- The address that bounced (the original recipient).
|
||||
bounced_address text NOT NULL,
|
||||
-- Which outbound surface the bounced message came from. NULL when we
|
||||
-- can't match the DSN's In-Reply-To / Message-Id back to a known row.
|
||||
original_send_type text,
|
||||
-- Surrogate id of the original send (document_sends.id /
|
||||
-- notifications.id / email_threads message id).
|
||||
original_send_id text,
|
||||
-- RFC 3464 DSN action/status. NULL when the upstream provider used a
|
||||
-- non-RFC bounce format.
|
||||
dsn_action text,
|
||||
dsn_status text,
|
||||
dsn_diagnostic text,
|
||||
received_at timestamptz NOT NULL DEFAULT now(),
|
||||
raw_message text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_email_bounces_port_received
|
||||
ON email_bounces (port_id, received_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_email_bounces_bounced_address
|
||||
ON email_bounces (bounced_address);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_email_bounces_original_send
|
||||
ON email_bounces (original_send_type, original_send_id)
|
||||
WHERE original_send_id IS NOT NULL;
|
||||
Reference in New Issue
Block a user