brackt/app/models/user.ts
Claude 3ba9d15f9e
Extract getUserDisplayName helper and use consistently throughout
Add a single getUserDisplayName(user) function to app/models/user.ts that
encapsulates the username → displayName fallback logic. Replace 9 scattered
inline expressions across the codebase (owner-map, scoring-calculator,
league routes, settings, invite flow, draft API, Clerk webhook) with calls
to the shared helper.

No behaviour change — all existing logic preserved, just centralised.

https://claude.ai/code/session_01VAkeDDVZMYS1DweQnUrRnH
2026-03-18 23:15:01 +00:00

154 lines
4.3 KiB
TypeScript

import { eq } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
export type User = typeof schema.users.$inferSelect;
export type NewUser = typeof schema.users.$inferInsert;
/**
* Returns the best available display identifier for a user.
* Prefers Clerk username; falls back to displayName (always set from
* firstName+lastName or email prefix). Returns null only if both are absent.
*/
export function getUserDisplayName(
user: Pick<User, "username" | "displayName">
): string | null {
return user.username ?? user.displayName ?? null;
}
export async function createUser(data: NewUser): Promise<User> {
const db = database();
const [user] = await db.insert(schema.users).values(data).returning();
return user;
}
export async function findUserById(id: string): Promise<User | undefined> {
const db = database();
return await db.query.users.findFirst({
where: eq(schema.users.id, id),
});
}
export async function findUserByClerkId(
clerkId: string
): Promise<User | undefined> {
const db = database();
return await db.query.users.findFirst({
where: eq(schema.users.clerkId, clerkId),
});
}
export async function updateUser(
id: string,
data: Partial<NewUser>
): Promise<User> {
const db = database();
const [user] = await db
.update(schema.users)
.set({ ...data, updatedAt: new Date() })
.where(eq(schema.users.id, id))
.returning();
return user;
}
export async function updateUserByClerkId(
clerkId: string,
data: Partial<NewUser>
): Promise<User> {
const db = database();
const [user] = await db
.update(schema.users)
.set({ ...data, updatedAt: new Date() })
.where(eq(schema.users.clerkId, clerkId))
.returning();
return user;
}
export async function findOrCreateUser(clerkUser: {
id: string;
emailAddresses?: Array<{ emailAddress: string }>;
username?: string | null;
firstName?: string | null;
lastName?: string | null;
imageUrl?: string;
}): Promise<User> {
// Try to find existing user
const existingUser = await findUserByClerkId(clerkUser.id);
const email = clerkUser.emailAddresses?.[0]?.emailAddress;
if (!email) {
throw new Error("User must have an email address");
}
// Generate display name: firstName lastName, or email username
let displayName = "";
if (clerkUser.firstName || clerkUser.lastName) {
displayName = [clerkUser.firstName, clerkUser.lastName]
.filter(Boolean)
.join(" ")
.trim();
} else {
// Use email username as fallback
displayName = email.split("@")[0];
}
if (existingUser) {
// Update user info in case it changed in Clerk
return await updateUserByClerkId(clerkUser.id, {
email,
username: clerkUser.username || undefined,
displayName,
firstName: clerkUser.firstName || undefined,
lastName: clerkUser.lastName || undefined,
imageUrl: clerkUser.imageUrl,
});
}
// Create new user
return await createUser({
clerkId: clerkUser.id,
email,
username: clerkUser.username || undefined,
displayName,
firstName: clerkUser.firstName || undefined,
lastName: clerkUser.lastName || undefined,
imageUrl: clerkUser.imageUrl,
});
}
export async function deleteUser(id: string): Promise<void> {
const db = database();
await db.delete(schema.users).where(eq(schema.users.id, id));
}
export async function findAdmins(): Promise<User[]> {
const db = database();
return await db.query.users.findMany({
where: eq(schema.users.isAdmin, true),
orderBy: (users, { asc }) => [asc(users.displayName)],
});
}
export async function isUserAdmin(userId: string): Promise<boolean> {
const user = await findUserById(userId);
return user?.isAdmin ?? false;
}
export async function isUserAdminByClerkId(clerkId: string): Promise<boolean> {
if (process.env.NODE_ENV === "development" && process.env.DEV_ADMIN_CLERK_ID === clerkId) {
return true;
}
const user = await findUserByClerkId(clerkId);
return user?.isAdmin ?? false;
}
export async function setUserAdmin(userId: string, isAdmin: boolean): Promise<User> {
return await updateUser(userId, { isAdmin });
}
export async function findAllUsers(): Promise<User[]> {
const db = database();
return await db.query.users.findMany({
orderBy: (users, { asc }) => [asc(users.displayName)],
});
}