diff --git a/packages/app/src/db/crud/user.ts b/packages/app/src/db/crud/user.ts index 48d92afd..b29afacf 100644 --- a/packages/app/src/db/crud/user.ts +++ b/packages/app/src/db/crud/user.ts @@ -39,14 +39,14 @@ export async function readUserById(id: string): Promise { export async function createUser(userInsert: UserInsert): Promise { // this is done in transaction to avoid race condition when creating user, for conflicts on authId return await db.transaction(async (trx) => { - // First try to find the user by authId + // Also check by email to handle edge cases const [existingUser] = await trx .select() .from(user) .where( and( - eq(user.authId, userInsert.authId), eq(user.email, userInsert.email), + eq(user.authId, userInsert.authId), ), ) .execute(); @@ -59,13 +59,13 @@ export async function createUser(userInsert: UserInsert): Promise { const [insertedUser] = await trx .insert(user) .values(userInsert) - .returning() .onConflictDoUpdate({ - target: [user.email], + target: [user.authId], set: { - authId: userInsert.authId, + email: userInsert.email, }, }) + .returning() .execute(); return insertedUser; diff --git a/packages/app/src/lib/auth.ts b/packages/app/src/lib/auth.ts index 90f765d9..27d17188 100644 --- a/packages/app/src/lib/auth.ts +++ b/packages/app/src/lib/auth.ts @@ -77,15 +77,50 @@ export const getServerUser = async (): Promise => { throw new Error(" New user has no email address"); } - const createdUser = await createUser({ - email, - authId, - firstName: clerkUser.firstName || "", - lastName: clerkUser.lastName || "", - picture: clerkUser.imageUrl, - privacyPolicyAcceptedAt: new Date(), - termsOfUseAcceptedAt: new Date(), - }); + // Retry mechanism for race conditions when creating new users + let createdUser: User | undefined; + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + createdUser = await createUser({ + email, + authId, + firstName: clerkUser.firstName || "", + lastName: clerkUser.lastName || "", + picture: clerkUser.imageUrl, + privacyPolicyAcceptedAt: new Date(), + termsOfUseAcceptedAt: new Date(), + }); + break; // Success, exit the retry loop + } catch (error: any) { + retryCount++; + + // If it's a duplicate key error and we haven't exceeded max retries, try again + if (retryCount < maxRetries) { + // Wait a bit before retrying (exponential backoff) + await new Promise((resolve) => + setTimeout(resolve, Math.pow(2, retryCount) * 100), + ); + + // Check if user was created by another request in the meantime + const existingUser = await readUserByAuthId(authId); + if (existingUser) { + createdUser = existingUser; + break; + } + continue; + } + + // If it's not a duplicate key error or we've exceeded retries, rethrow + throw error; + } + } + + if (!createdUser) { + throw new Error("Failed to create user after multiple attempts"); + } // Identify newly created user in PostHog await identifyUserServer(createdUser, {