Fix migrate.mjs: run FK conversion unconditionally, not gated on CLERK_SECRET_KEY

The FK conversion (teams.owner_id, commissioners.user_id → UUIDs) and
email_verified update were inside runClerkMigration() which only ran when
CLERK_SECRET_KEY was set. If the key wasn't in the environment at deploy
time, all Clerk IDs were left unconverted, causing UUID parse errors at
runtime.

Split into two functions:
- convertForeignKeys(): always runs, idempotent WHERE clauses
- importClerkOAuthAccounts(): only runs when CLERK_SECRET_KEY is set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-04-24 22:41:41 -07:00
parent 65478e1ef2
commit cd8074ef10

View file

@ -30,20 +30,29 @@ try {
} }
// ── Clerk → BetterAuth one-time data migration ──────────────────────────────── // ── Clerk → BetterAuth one-time data migration ────────────────────────────────
// Runs on every deploy but is fully idempotent. Skips automatically once // FK conversion and email_verified always run (idempotent WHERE clauses).
// CLERK_SECRET_KEY is removed from the environment. // OAuth account import only runs when CLERK_SECRET_KEY is set.
console.log("Running FK conversion...");
try {
await convertForeignKeys(client);
} catch (err) {
console.error("FK conversion failed:", err);
await client.end().catch(() => {});
process.exit(1);
}
const CLERK_SECRET_KEY = process.env.CLERK_SECRET_KEY; const CLERK_SECRET_KEY = process.env.CLERK_SECRET_KEY;
if (!CLERK_SECRET_KEY) { if (!CLERK_SECRET_KEY) {
console.log("CLERK_SECRET_KEY not set — skipping Clerk account migration"); console.log("CLERK_SECRET_KEY not set — skipping Clerk OAuth account import");
} else { } else {
console.log("Running Clerk account migration..."); console.log("Running Clerk OAuth account import...");
try { try {
await runClerkMigration(client, CLERK_SECRET_KEY); await importClerkOAuthAccounts(client, CLERK_SECRET_KEY);
console.log("Clerk account migration complete"); console.log("Clerk OAuth account import complete");
} catch (err) { } catch (err) {
console.error("Clerk account migration failed:", err); console.error("Clerk OAuth account import failed:", err);
await client.end().catch(() => {}); await client.end().catch(() => {});
process.exit(1); process.exit(1);
} }
@ -54,9 +63,9 @@ process.exit(0);
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
async function runClerkMigration(sql, secretKey) { async function convertForeignKeys(sql) {
// 1. Convert teams.owner_id from Clerk IDs to users.id UUIDs // Convert teams.owner_id from Clerk IDs to users.id UUIDs.
// Safe to run repeatedly — WHERE clause only matches unconverted rows. // Safe to run repeatedly — WHERE clause only matches unconverted rows.
const teamsResult = await sql` const teamsResult = await sql`
UPDATE teams t UPDATE teams t
SET owner_id = u.id::text SET owner_id = u.id::text
@ -66,7 +75,7 @@ async function runClerkMigration(sql, secretKey) {
`; `;
console.log(` teams.owner_id: ${teamsResult.count} rows converted`); console.log(` teams.owner_id: ${teamsResult.count} rows converted`);
// 2. Convert commissioners.user_id from Clerk IDs to users.id UUIDs // Convert commissioners.user_id from Clerk IDs to users.id UUIDs
const commissResult = await sql` const commissResult = await sql`
UPDATE commissioners c UPDATE commissioners c
SET user_id = u.id::text SET user_id = u.id::text
@ -76,15 +85,17 @@ async function runClerkMigration(sql, secretKey) {
`; `;
console.log(` commissioners.user_id: ${commissResult.count} rows converted`); console.log(` commissioners.user_id: ${commissResult.count} rows converted`);
// 3. Mark all Clerk users as email-verified (they already verified via Clerk) // Mark all Clerk users as email-verified (they already verified via Clerk)
await sql` await sql`
UPDATE users UPDATE users
SET email_verified = true SET email_verified = true
WHERE clerk_id IS NOT NULL WHERE clerk_id IS NOT NULL
AND email_verified = false AND email_verified = false
`; `;
}
// 4. Migrate OAuth accounts from Clerk into BetterAuth's accounts table. async function importClerkOAuthAccounts(sql, secretKey) {
// Migrate OAuth accounts from Clerk into BetterAuth's accounts table.
// Skip entirely if any OAuth accounts already exist to avoid duplicates. // Skip entirely if any OAuth accounts already exist to avoid duplicates.
// We check only OAuth providers (not 'credential') so running migrate-clerk-passwords.mjs // We check only OAuth providers (not 'credential') so running migrate-clerk-passwords.mjs
// first doesn't prevent OAuth accounts from being created. // first doesn't prevent OAuth accounts from being created.