51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
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);
|
|
});
|
|
};
|