import { clerkClient } from "@clerk/nextjs/server"; import log from "./logger"; /** * User roles available in the application */ export type UserRole = "admin" | "trainer" | "client"; /** * Set a user's role in Clerk public metadata * This will trigger a webhook that syncs the role to the database * * @param userId - Clerk user ID * @param role - Role to assign (admin, trainer, or client) * @returns Updated user object * * @example * await setUserRole('user_abc123', 'admin'); */ export async function setUserRole(userId: string, role: UserRole) { const client = await clerkClient(); return await client.users.updateUser(userId, { publicMetadata: { role }, }); } /** * Get a user's role from Clerk public metadata * * @param userId - Clerk user ID * @returns User role or null if not set * * @example * const role = await getUserRole('user_abc123'); * console.log(role); // 'admin' | 'trainer' | 'client' | null */ export async function getUserRole(userId: string): Promise { const client = await clerkClient(); const user = await client.users.getUser(userId); const role = user.publicMetadata?.role as UserRole | undefined; return role || null; } /** * Check if a user has a specific role * * @param userId - Clerk user ID * @param role - Role to check * @returns True if user has the role * * @example * const isAdmin = await hasRole('user_abc123', 'admin'); */ export async function hasRole( userId: string, role: UserRole, ): Promise { const userRole = await getUserRole(userId); return userRole === role; } /** * Check if a user is an admin * * @param userId - Clerk user ID * @returns True if user is an admin * * @example * const isAdmin = await isAdmin('user_abc123'); */ export async function isAdmin(userId: string): Promise { return hasRole(userId, "admin"); } /** * Check if a user is a trainer * * @param userId - Clerk user ID * @returns True if user is a trainer * * @example * const isTrainer = await isTrainer('user_abc123'); */ export async function isTrainer(userId: string): Promise { return hasRole(userId, "trainer"); } /** * Check if a user is a client * * @param userId - Clerk user ID * @returns True if user is a client * * @example * const isClient = await isClient('user_abc123'); */ export async function isClient(userId: string): Promise { return hasRole(userId, "client"); } /** * Bulk update roles for multiple users * * @param userRoles - Array of {userId, role} objects * @returns Array of updated users * * @example * await bulkSetUserRoles([ * { userId: 'user_abc123', role: 'admin' }, * { userId: 'user_def456', role: 'trainer' }, * ]); */ export async function bulkSetUserRoles( userRoles: Array<{ userId: string; role: UserRole }>, ): Promise { const client = await clerkClient(); await Promise.all( userRoles.map(({ userId, role }) => client.users.updateUser(userId, { publicMetadata: { role }, }), ), ); } /** * Get all users with a specific role * * @param role - Role to filter by * @returns Array of users with the specified role * * @example * const admins = await getUsersByRole('admin'); */ export async function getUsersByRole(role: UserRole) { const client = await clerkClient(); // Clerk doesn't support filtering by metadata directly, so we fetch all users // and filter client-side. For large user bases, consider caching or database queries. const { data: users } = await client.users.getUserList(); return users.filter( (user) => (user.publicMetadata?.role as UserRole) === role, ); } /** * Get user count by role * * @returns Object with counts for each role * * @example * const counts = await getUserCountByRole(); * console.log(counts); // { admin: 2, trainer: 5, client: 100 } */ export async function getUserCountByRole(): Promise> { const client = await clerkClient(); const { data: users } = await client.users.getUserList(); const counts: Record = { admin: 0, trainer: 0, client: 0, }; users.forEach((user) => { const role = (user.publicMetadata?.role as UserRole) || "client"; counts[role]++; }); return counts; } /** * Sync user role between Clerk and database manually * Useful for fixing inconsistencies or after manual changes * * @param userId - Clerk user ID * @returns Success status * * @example * await syncUserRole('user_abc123'); */ export async function syncUserRole(userId: string): Promise { try { const client = await clerkClient(); const user = await client.users.getUser(userId); // Trigger a webhook by updating the user (even with same data) await client.users.updateUser(userId, { publicMetadata: user.publicMetadata, }); return true; } catch (error) { log.error("Failed to sync user role", error, { userId }); return false; } }