From a893d9538731c2e43868288013619bb9d8675d05 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Fri, 27 Feb 2026 18:24:47 -0800 Subject: [PATCH] fix: reliable fs_users upsert on sign-in ON CONFLICT expression matching was silently failing due to a mismatch between the query expression and the index definition (::text cast). Replaced with an explicit SELECT-then-INSERT-or-UPDATE pattern. Made-with: Cursor --- lib/auth/authOptions.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/auth/authOptions.ts b/lib/auth/authOptions.ts index 07c62d4..b41486e 100644 --- a/lib/auth/authOptions.ts +++ b/lib/auth/authOptions.ts @@ -28,15 +28,31 @@ export const authOptions: NextAuthOptions = { async signIn({ user }) { if (!user?.email) return true; try { - const workspace = user.email.split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, "-") + "-account"; - await query(` - INSERT INTO fs_users (id, user_id, data) - VALUES (gen_random_uuid()::text, $1, $2::jsonb) - ON CONFLICT ((data->>'email')) DO UPDATE - SET user_id = EXCLUDED.user_id, - data = fs_users.data || EXCLUDED.data, - updated_at = NOW() - `, [user.id, JSON.stringify({ email: user.email, name: user.name, image: user.image, workspace })]); + const workspace = + user.email.split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, "-") + "-account"; + const data = JSON.stringify({ + email: user.email, + name: user.name, + image: user.image, + workspace, + }); + + // Two-step upsert avoids relying on ON CONFLICT expression matching + const existing = await query<{ id: string }>( + `SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1`, + [user.email] + ); + if (existing.length === 0) { + await query( + `INSERT INTO fs_users (id, user_id, data) VALUES (gen_random_uuid()::text, $1, $2::jsonb)`, + [user.id, data] + ); + } else { + await query( + `UPDATE fs_users SET user_id = $1, data = data || $2::jsonb, updated_at = NOW() WHERE id = $3`, + [user.id, data, existing[0].id] + ); + } } catch (e) { console.error("[signIn] Failed to upsert fs_user:", e); }