import { eq } from 'drizzle-orm' import { createHash, randomUUID } from 'node:crypto' import type { AppDb } from './index.js' import { refreshSessions, userApps, userPermissions, users, } from './schema/index.js' export type UserRow = typeof users.$inferSelect export function hashToken(token: string): string { return createHash('sha256').update(token).digest('hex') } export function listUsers(db: AppDb): UserRow[] { return db.select().from(users).all() } export function getUserById(db: AppDb, id: string): UserRow | undefined { return db.select().from(users).where(eq(users.id, id)).get() } export function getUserByEmail( db: AppDb, email: string, ): UserRow | undefined { return db .select() .from(users) .where(eq(users.email, email.toLowerCase())) .get() } export function getUserApps(db: AppDb, userId: string): string[] { return db .select() .from(userApps) .where(eq(userApps.userId, userId)) .all() .map((r) => r.appId) } export function getUserPermissions(db: AppDb, userId: string): string[] { return db .select() .from(userPermissions) .where(eq(userPermissions.userId, userId)) .all() .map((r) => r.permission) } export function createUser( db: AppDb, input: { email: string name: string passwordHash: string isAdmin: boolean }, ): UserRow { const now = new Date().toISOString() const id = randomUUID() db.insert(users) .values({ id, email: input.email.toLowerCase(), name: input.name, passwordHash: input.passwordHash, isAdmin: input.isAdmin, disabled: false, createdAt: now, updatedAt: now, }) .run() return getUserById(db, id)! } export function updateUser( db: AppDb, id: string, patch: { email?: string name?: string passwordHash?: string isAdmin?: boolean disabled?: boolean }, ): UserRow | undefined { const existing = getUserById(db, id) if (!existing) return undefined const now = new Date().toISOString() db.update(users) .set({ email: patch.email?.toLowerCase() ?? existing.email, name: patch.name ?? existing.name, passwordHash: patch.passwordHash ?? existing.passwordHash, isAdmin: patch.isAdmin ?? existing.isAdmin, disabled: patch.disabled ?? existing.disabled, updatedAt: now, }) .where(eq(users.id, id)) .run() return getUserById(db, id) } export function deleteUser(db: AppDb, id: string): boolean { const result = db.delete(users).where(eq(users.id, id)).run() return result.changes > 0 } export function setUserAccess( db: AppDb, userId: string, apps: string[], permissions: string[], ): void { db.delete(userApps).where(eq(userApps.userId, userId)).run() db.delete(userPermissions).where(eq(userPermissions.userId, userId)).run() for (const appId of apps) { db.insert(userApps).values({ userId, appId }).run() } for (const permission of permissions) { db.insert(userPermissions).values({ userId, permission }).run() } db.update(users) .set({ updatedAt: new Date().toISOString() }) .where(eq(users.id, userId)) .run() } export function createRefreshSession( db: AppDb, userId: string, rawToken: string, expiresAt: Date, ): void { db.insert(refreshSessions) .values({ id: randomUUID(), userId, tokenHash: hashToken(rawToken), expiresAt: expiresAt.toISOString(), revokedAt: null, createdAt: new Date().toISOString(), }) .run() } export function revokeRefreshSession(db: AppDb, rawToken: string): void { const now = new Date().toISOString() db.update(refreshSessions) .set({ revokedAt: now }) .where(eq(refreshSessions.tokenHash, hashToken(rawToken))) .run() }