From 446342aa696334af58fa5bf3aed12e50b988d81c Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 14 May 2026 22:37:47 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20P0=20=E2=80=94=20bootstrap=20proxy=20+?= =?UTF-8?q?=20interest=20detail=20Date=20crash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two pre-deploy blockers found during click-testing: 1. /api/v1/bootstrap/status returned 401 to anonymous visitors because /api/v1/bootstrap/ was not in proxy.ts's PUBLIC_PATHS allow-list. Fresh VPS deploys couldn't bootstrap their first super-admin via /setup — the page reads bootstrap status to decide whether to render the form and got no signal back. The route handlers self-protect via hasAnySuperAdmin(). 2. getInterestById() crashed every interest detail request with `CONNECT_TIMEOUT` / "string argument must be of type string or Buffer" because the contact-log count query passed a raw Date through a sql template fragment. postgres-js's Bind step can't serialize a Date that way. Switched to drizzle's gte() operator which routes the value through the column-aware serializer. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/services/interests.service.ts | 7 ++----- src/proxy.ts | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/services/interests.service.ts b/src/lib/services/interests.service.ts index 16347dac..c2210280 100644 --- a/src/lib/services/interests.service.ts +++ b/src/lib/services/interests.service.ts @@ -1,4 +1,4 @@ -import { and, desc, eq, exists, inArray, isNull, ne, sql } from 'drizzle-orm'; +import { and, desc, eq, exists, gte, inArray, isNull, ne, sql } from 'drizzle-orm'; import { db } from '@/lib/db'; import { interests, interestBerths, interestTags, interestNotes } from '@/lib/db/schema/interests'; @@ -561,10 +561,7 @@ export async function getInterestById(id: string, portId: string) { .select({ count: sql`count(*)::int` }) .from(interestContactLog) .where( - and( - eq(interestContactLog.interestId, id), - sql`${interestContactLog.occurredAt} >= ${sevenDaysAgo}`, - ), + and(eq(interestContactLog.interestId, id), gte(interestContactLog.occurredAt, sevenDaysAgo)), ); // Resolve the assignee's display name for the header chip — falling back diff --git a/src/proxy.ts b/src/proxy.ts index c36fa951..26efc21f 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -57,6 +57,10 @@ const PUBLIC_PATHS: string[] = [ '/api/public/', '/api/health', '/api/webhooks/', + // First-run / cold-start: the unauthenticated /setup and /login pages + // call /api/v1/bootstrap/status to decide whether to render the setup + // form. The route handlers self-protect via hasAnySuperAdmin(). + '/api/v1/bootstrap/', '/scan', '/portal/', '/api/portal/',