import { eq, inArray, and } from "drizzle-orm"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { generateFlagConfig } from "~/lib/flag-generator"; export type User = typeof schema.users.$inferSelect; export type NewUser = typeof schema.users.$inferInsert; export const USERNAME_RE = /^[a-zA-Z0-9_-]{3,30}$/; export function getUserDisplayName( user: Pick ): string | null { return user.username ?? user.displayName ?? null; } export async function createUser(data: NewUser): Promise { const db = database(); const id = data.id ?? crypto.randomUUID(); const [user] = await db .insert(schema.users) .values({ ...data, id, flagConfig: data.flagConfig ?? generateFlagConfig(id) }) .returning(); return user; } export async function findUserById(id: string): Promise { const db = database(); return await db.query.users.findFirst({ where: eq(schema.users.id, id), }); } export async function findUsersByIds(ids: string[]): Promise { if (ids.length === 0) return []; const db = database(); return await db .select() .from(schema.users) .where(inArray(schema.users.id, ids)); } export async function findUserByUsername(username: string): Promise { const db = database(); return await db.query.users.findFirst({ where: (users, { sql }) => sql`lower(${users.username}) = lower(${username})`, }); } export async function updateUser( id: string, data: Partial ): Promise { 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 anonymizeUserAccount(id: string): Promise { const db = database(); const shortId = id.replace(/-/g, "").slice(0, 8); await db.transaction(async (tx) => { await tx.delete(schema.sessions).where(eq(schema.sessions.userId, id)); await tx.delete(schema.accounts).where(eq(schema.accounts.userId, id)); await tx .update(schema.users) .set({ email: `deleted-${shortId}@deleted.brackt.com`, displayName: null, username: null, imageUrl: null, customAvatarUrl: null, flagConfig: null, avatarType: "flag", deletedAt: new Date(), updatedAt: new Date(), }) .where(eq(schema.users.id, id)); }); } export async function findAdmins(): Promise { 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 { const user = await findUserById(userId); return user?.isAdmin ?? false; } export async function setUserAdmin(userId: string, isAdmin: boolean): Promise { return await updateUser(userId, { isAdmin }); } export async function findAllUsers(): Promise { const db = database(); return await db.query.users.findMany({ orderBy: (users, { asc }) => [asc(users.displayName)], }); } /** * Returns true if the user currently owns a team in a draft-status season. * Used to prevent timezone changes mid-draft. */ export async function isUserInActiveDraft(userId: string): Promise { const db = database(); const result = await db .select({ id: schema.teams.id }) .from(schema.teams) .innerJoin(schema.seasons, eq(schema.teams.seasonId, schema.seasons.id)) .where( and( eq(schema.teams.ownerId, userId), eq(schema.seasons.status, "draft") ) ) .limit(1); return result.length > 0; }