import { getRepository } from '@/lib/db/client'; import { SessionClaims } from '@/lib/feature/user/clerk.model'; import { UserEntity } from '@/lib/feature/user/user.entity'; import { CreateUserModel, UpdateUserModel, UserModel, } from '@/lib/feature/user/user.model'; import { TypedResult, wrap } from '@/utils/types/results'; export const userEntityToModel = (userEntity: UserEntity): UserModel => { return { id: userEntity.id, name: userEntity.name, email: userEntity.email, role: userEntity.role, externalId: userEntity.externalId, }; }; /** * Retrieves a user by their email address. * @param email - The email address of the user to retrieve. * @returns {Promise>} The user model if found, otherwise null. */ export const getUserByEmail = wrap( async (email: string): Promise => { const userRepository = await getRepository(UserEntity); const userEntity = await userRepository.findOneBy({ email }); if (!userEntity) { return null; } return userEntityToModel(userEntity); } ); /** * Saves a new user to the database. * @param user - The user data to save. * @returns {Promise>} The saved user model. */ export const saveUser = wrap( async (user: CreateUserModel): Promise => { const userRepository = await getRepository(UserEntity); if (!!(await userRepository.findOneBy({ email: user.email }))) { throw new Error(`User with email ${user.email} already exists`); } const newUser = userRepository.create(user); return userEntityToModel(await userRepository.save(newUser)); } ); /** * Updates an existing user in the database. * @param userId - The ID of the user to update. * @param user - The new user data. * @returns {Promise>} The updated user model. */ export const updateUser = wrap( async (userId: string, user: UpdateUserModel): Promise => { const userRepository = await getRepository(UserEntity); const existingUser = await userRepository.findOneBy({ id: userId }); if (!existingUser) { throw new Error(`User with ID ${userId} not found`); } if (!!user.email) existingUser.email = user.email; if (!!user.name) existingUser.name = user.name; if (!!user.role) existingUser.role = user.role; return userEntityToModel(await userRepository.save(existingUser)); } ); /** * Synchronizes a user with the database. * If the user already exists, updates it; if not, creates a new record. * @param sessionClaims Session Claims from the Auth Provider * @returns {Promise>} The synchronized user model. */ export const syncUser = wrap( async (sessionClaims: SessionClaims): Promise => { const { full_name, email } = sessionClaims.user; const role = sessionClaims.user.public_metadata.role; const existingUserResult = await getUserByEmail(email); if (!existingUserResult.ok || !existingUserResult.value) { const saveResult = await saveUser({ name: full_name, email: email, role: role, }); if (!saveResult.ok) { throw new Error(`User with email ${email} already exists`); } return saveResult.value; } const existingUser = existingUserResult.value; const updateResult = await updateUser(existingUser.id, { name: full_name, email: existingUser.email, role: role, }); if (!updateResult.ok) { throw new Error( `Failed to update user with email ${email}: ${updateResult.error}` ); } return updateResult.value; } ); // Explicit re-export for TypeScript consumers who need the result type export type { TypedResult };