import { randomBytes } from "node:crypto"; import { count, eq } from "drizzle-orm"; import { db as prodDb } from "../../db/index.ts"; import { apiKeys, sessions, users } from "../../db/schema.ts"; type Db = typeof prodDb; // ── User Management ────────────────────────────────────────────────── export async function createUser( db: Db = prodDb, username: string, password: string, ) { const passwordHash = await Bun.password.hash(password); return db.insert(users).values({ username, passwordHash }).returning().get(); } export async function verifyPassword( db: Db = prodDb, username: string, password: string, ) { const user = db .select() .from(users) .where(eq(users.username, username)) .get(); if (!user) return null; const valid = await Bun.password.verify(password, user.passwordHash); return valid ? user : null; } export function getUserCount(db: Db = prodDb): number { const result = db.select({ value: count() }).from(users).get(); return result?.value ?? 0; } export async function changePassword( db: Db = prodDb, username: string, currentPassword: string, newPassword: string, ): Promise { const user = await verifyPassword(db, username, currentPassword); if (!user) return false; const newHash = await Bun.password.hash(newPassword); db.update(users) .set({ passwordHash: newHash }) .where(eq(users.id, user.id)) .run(); return true; } // ── Session Management ─────────────────────────────────────────────── export function createSession( db: Db = prodDb, userId: number, expiryDays = 30, ) { const id = randomBytes(32).toString("hex"); const expiresAt = new Date(Date.now() + expiryDays * 24 * 60 * 60 * 1000); return db .insert(sessions) .values({ id, userId, expiresAt }) .returning() .get(); } export function getSession(db: Db = prodDb, sessionId: string) { const session = db .select() .from(sessions) .where(eq(sessions.id, sessionId)) .get(); if (!session) return null; if (session.expiresAt < new Date()) { db.delete(sessions).where(eq(sessions.id, sessionId)).run(); return null; } return session; } export function deleteSession(db: Db = prodDb, sessionId: string) { db.delete(sessions).where(eq(sessions.id, sessionId)).run(); } export function refreshSession( db: Db = prodDb, sessionId: string, expiryDays = 30, ) { const expiresAt = new Date(Date.now() + expiryDays * 24 * 60 * 60 * 1000); db.update(sessions) .set({ expiresAt }) .where(eq(sessions.id, sessionId)) .run(); } // ── API Key Management ─────────────────────────────────────────────── export async function createApiKey(db: Db = prodDb, name: string) { const rawKey = randomBytes(32).toString("hex"); const keyHash = await Bun.password.hash(rawKey); const keyPrefix = rawKey.slice(0, 8); const record = db .insert(apiKeys) .values({ name, keyHash, keyPrefix }) .returning() .get(); return { ...record, rawKey }; } export async function verifyApiKey( db: Db = prodDb, rawKey: string, ): Promise { const prefix = rawKey.slice(0, 8); const candidates = db .select() .from(apiKeys) .where(eq(apiKeys.keyPrefix, prefix)) .all(); for (const candidate of candidates) { const valid = await Bun.password.verify(rawKey, candidate.keyHash); if (valid) return true; } return false; } export function listApiKeys(db: Db = prodDb) { return db .select({ id: apiKeys.id, name: apiKeys.name, keyPrefix: apiKeys.keyPrefix, createdAt: apiKeys.createdAt, }) .from(apiKeys) .all(); } export function deleteApiKey(db: Db = prodDb, id: number) { db.delete(apiKeys).where(eq(apiKeys.id, id)).run(); }