fitaiProto/apps/admin/src/lib/clerk-helpers.ts
echo 3a58d420d6 clerkauth
implemented, sync with db to be added
2025-11-10 04:16:31 +01:00

199 lines
4.8 KiB
TypeScript

import { clerkClient } from '@clerk/nextjs/server';
/**
* 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<UserRole | null> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<void> {
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<Record<UserRole, number>> {
const client = await clerkClient();
const { data: users } = await client.users.getUserList();
const counts: Record<UserRole, number> = {
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<boolean> {
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) {
console.error('Error syncing user role:', error);
return false;
}
}