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:
@@ -69,7 +69,13 @@ export async function GET(
|
||||
const [berth] = await db
|
||||
.select()
|
||||
.from(berths)
|
||||
.where(and(eq(berths.portId, port.id), eq(berths.mooringNumber, mooringNumber)))
|
||||
.where(
|
||||
and(
|
||||
eq(berths.portId, port.id),
|
||||
eq(berths.mooringNumber, mooringNumber),
|
||||
isNull(berths.archivedAt),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!berth) {
|
||||
|
||||
@@ -72,13 +72,12 @@ export async function GET(request: Request): Promise<Response> {
|
||||
);
|
||||
}
|
||||
|
||||
// 1. Active berths for the port (archived would be an explicit field
|
||||
// once we add one - today we don't have an archived_at on berths,
|
||||
// so we surface every row except those marked status='sold' on
|
||||
// request? No: §4.5 says "filters out berths archived in CRM".
|
||||
// The current schema has no archived flag for berths, so this is
|
||||
// a no-op today; future archive flag plugs in here.
|
||||
const berthRows = await db.select().from(berths).where(eq(berths.portId, port.id));
|
||||
// 1. Active berths for the port — retired moorings are hidden via
|
||||
// the archived_at soft-delete column (migration 0065).
|
||||
const berthRows = await db
|
||||
.select()
|
||||
.from(berths)
|
||||
.where(and(eq(berths.portId, port.id), isNull(berths.archivedAt)));
|
||||
|
||||
if (berthRows.length === 0) {
|
||||
return jsonResponse({ list: [], pageInfo: emptyPageInfo() });
|
||||
|
||||
Reference in New Issue
Block a user