74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
import { currentUser } from "@clerk/nextjs/server";
|
|
import type { IDatabase, User } from "./database/types";
|
|
import log from "./logger";
|
|
|
|
export async function ensureUserSynced(
|
|
userId: string,
|
|
db: IDatabase,
|
|
): Promise<User | null> {
|
|
const existingUser = await db.getUserById(userId);
|
|
if (existingUser) return existingUser;
|
|
|
|
log.debug("User not found in DB by ID, checking Clerk", { userId });
|
|
const clerkUser = await currentUser();
|
|
|
|
if (!clerkUser || clerkUser.id !== userId) {
|
|
// If we can't get the user from Clerk (e.g. running locally without full auth sync),
|
|
// we might want to fail gracefully or throw.
|
|
// For now, throw to be safe.
|
|
throw new Error("Could not fetch Clerk user details");
|
|
}
|
|
|
|
const email = clerkUser.emailAddresses[0]?.emailAddress;
|
|
if (!email) throw new Error("User has no email");
|
|
|
|
// Check if user exists by email (e.g. seeded user)
|
|
const existingByEmail = await db.getUserByEmail(email);
|
|
if (existingByEmail) {
|
|
log.debug("User found by email but ID mismatch, migrating ID", {
|
|
oldId: existingByEmail.id,
|
|
newId: userId,
|
|
});
|
|
|
|
// Update the ID to match Clerk ID
|
|
// We need to do this manually via SQL because IDatabase interface might not expose a direct ID update method easily
|
|
// But we can use a raw query if we had access, or add a method.
|
|
// Since we don't have direct access to `db.db` here (it's hidden behind IDatabase),
|
|
// we should add a method to IDatabase or use a workaround.
|
|
// Actually, `SQLiteDatabase` is what we have at runtime.
|
|
// Let's assume we can cast it or add `updateUserId` to the interface.
|
|
|
|
// For now, let's try to update it using `updateUser` but `updateUser` usually updates fields based on ID.
|
|
// We can't update the ID itself using `updateUser(id, { id: newId })` because `updateUser` implementation filters out `id` from updates.
|
|
|
|
// We need to add a method to migrate user ID in IDatabase.
|
|
// Or, strictly for this prototype, we can delete the old user and create a new one (DATA LOSS RISK).
|
|
// But since it's a seeded super admin with no data, it's fine.
|
|
// However, if they had clients, we'd lose the link.
|
|
|
|
// Let's add `migrateUserId(oldId, newId)` to IDatabase.
|
|
await db.migrateUserId(existingByEmail.id, userId);
|
|
return db.getUserById(userId);
|
|
}
|
|
|
|
log.debug("Creating new user from Clerk data", { userId, email });
|
|
const user = await db.createUser({
|
|
id: userId,
|
|
email,
|
|
firstName: clerkUser.firstName || "",
|
|
lastName: clerkUser.lastName || "",
|
|
password: "", // Managed by Clerk
|
|
phone: clerkUser.phoneNumbers[0]?.phoneNumber || undefined,
|
|
gymId: (clerkUser.publicMetadata.gymId as string | undefined) || undefined,
|
|
role: (() => {
|
|
const r = clerkUser.publicMetadata.role as string | undefined;
|
|
const validRoles = ["superAdmin", "admin", "trainer", "client"] as const;
|
|
return r && validRoles.includes(r as (typeof validRoles)[number])
|
|
? (r as (typeof validRoles)[number])
|
|
: "client";
|
|
})(),
|
|
});
|
|
|
|
return user;
|
|
}
|