fix(water-log): remove nested Promise.all in field PIN login
Deploy to route.crispygoat.com / deploy (push) Successful in 5m3s
Deploy to route.crispygoat.com / deploy (push) Successful in 5m3s
Replace nested Promise.all([insert, update]) + outer Promise.all([withBrand, cookies()]) with sequential awaits in verifyWaterPin, mirroring the admin-portal pattern from commitca79896and src/lib/water-admin-pin-auth.ts. Symptom: field PIN works once, fails after logout until reset. Root cause is the same family flagged inca79896: cookies() corrupts Next.js's request-scoped AsyncLocalStorage when called from inside a pg transaction, and nested Promise.all inside the transaction makes the failure non-deterministic. Also adds scripts/diag-water-pin.mjs for one-shot prod DB inspection if the symptom persists post-deploy.
This commit is contained in:
@@ -144,32 +144,40 @@ export async function verifyWaterPin(
|
||||
}
|
||||
|
||||
// Create a session in the user's brand context. The session insert
|
||||
// and the last_used_at bump are independent writes — fire them in
|
||||
// parallel and wait on both before returning.
|
||||
const [sessionId, cookieStore] = await Promise.all([
|
||||
withBrand(match.brandId, async (db) => {
|
||||
const expiresAt = new Date(
|
||||
Date.now() + FIELD_SESSION_HOURS * 60 * 60 * 1000,
|
||||
);
|
||||
const [insertResult] = await Promise.all([
|
||||
db
|
||||
.insert(waterSessions)
|
||||
.values({
|
||||
irrigatorId: match.id,
|
||||
expiresAt,
|
||||
})
|
||||
.returning({ id: waterSessions.id }),
|
||||
db
|
||||
.update(waterIrrigators)
|
||||
.set({ lastUsedAt: new Date() })
|
||||
.where(eq(waterIrrigators.id, match.id)),
|
||||
]);
|
||||
const row = insertResult[0];
|
||||
if (!row) throw new Error("Failed to create session");
|
||||
return row.id;
|
||||
}),
|
||||
cookies(),
|
||||
]);
|
||||
// and the last_used_at bump run *sequentially* on the same pg
|
||||
// connection inside the `withBrand` transaction — never concurrently.
|
||||
//
|
||||
// The previous shape used a nested `Promise.all([insert, update])`
|
||||
// *and* an outer `Promise.all([withBrand, cookies()])`. That was
|
||||
// known-fragile per the codebase's own commit `ca79896`: "cookies()
|
||||
// corrupts Next.js's request-scoped AsyncLocalStorage if called from
|
||||
// inside a pg transaction." The admin portal has been on the
|
||||
// sequential shape (see `src/lib/water-admin-pin-auth.ts`) since
|
||||
// that commit; this brings the field portal in line.
|
||||
const sessionId = await withBrand(match.brandId, async (db) => {
|
||||
const expiresAt = new Date(
|
||||
Date.now() + FIELD_SESSION_HOURS * 60 * 60 * 1000,
|
||||
);
|
||||
const [inserted] = await db
|
||||
.insert(waterSessions)
|
||||
.values({
|
||||
irrigatorId: match.id,
|
||||
expiresAt,
|
||||
})
|
||||
.returning({ id: waterSessions.id });
|
||||
if (!inserted) throw new Error("Failed to create session");
|
||||
|
||||
await db
|
||||
.update(waterIrrigators)
|
||||
.set({ lastUsedAt: new Date() })
|
||||
.where(eq(waterIrrigators.id, match.id));
|
||||
|
||||
return inserted.id;
|
||||
});
|
||||
|
||||
// Cookie set is fully outside the DB transaction. Same rule as
|
||||
// `verifyAdminPin` (see comment in `src/lib/water-admin-pin-auth.ts`).
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set("wl_session", sessionId, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
|
||||
Reference in New Issue
Block a user