fix: P0 — bootstrap proxy + interest detail Date crash

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 22:37:47 +02:00
parent b2ba0b4e0a
commit 446342aa69
2 changed files with 6 additions and 5 deletions

View File

@@ -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<number>`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

View File

@@ -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/',