import { eq } from 'drizzle-orm'; import { agents, files, models, providers, settings, toolCalls, topics, users, } from '~~/drizzle/schema'; import { db } from '~~/server/lib/db'; import { hasUsableAdministrator, lockAccount, lockAdministration } from "~~/server/lib/account-security"; import { Err, Ok } from "~~/types/result"; /** * Deletes an account and everything that belongs to it. * * The app tables only store a plain user id, nothing cascades from the users * table, so every one of them is cleared here. It all happens in one * transaction: either the whole account is gone or nothing changed. * * Objects in file storage are left alone on purpose. */ export const deleteAccount = async (userId: string) => { return await db.transaction(async (tx) => { await lockAdministration(tx); await lockAccount(tx, userId); const [account] = await tx.select({ role: users.role }).from(users).where(eq(users.id, userId)); if (account?.role === "admin" && !await hasUsableAdministrator(tx, userId)) { return Err("LAST_ADMIN" as const); } // messages, their parts, generations and attachments cascade from topics await tx.delete(topics).where(eq(topics.userId, userId)); await tx.delete(agents).where(eq(agents.userId, userId)); await tx.delete(models).where(eq(models.userId, userId)); await tx.delete(providers).where(eq(providers.userId, userId)); // nothing points a tool call back at its message, so these never cascade await tx.delete(toolCalls).where(eq(toolCalls.userId, userId)); // embeddings go with their file await tx.delete(files).where(eq(files.userId, userId)); await tx.delete(settings).where(eq(settings.userId, userId)); // sessions, accounts and two-factor rows cascade from the user await tx.delete(users).where(eq(users.id, userId)); return Ok(undefined); }); };