feat: add purgeContent option to tombstoneUser for soft-delete mode

This commit is contained in:
Jannis Braun
2026-04-03 02:30:00 +02:00
parent 867caa5f31
commit bb4b031423
+12 -1
View File
@@ -2,6 +2,11 @@ import crypto from 'crypto';
import { eq, or, and, inArray } from 'drizzle-orm'; import { eq, or, and, inArray } from 'drizzle-orm';
import { getDb, schema } from '../db/index.js'; import { getDb, schema } from '../db/index.js';
export interface TombstoneOptions {
/** When false, skip reaction deletion and orphaned DM purge (soft-delete mode). Default: true */
purgeContent?: boolean;
}
/** /**
* Tombstone a user account: removes them from all spaces, DMs, friends, * Tombstone a user account: removes them from all spaces, DMs, friends,
* roles, reactions, folders, bans, voice restrictions, channel overrides, * roles, reactions, folders, bans, voice restrictions, channel overrides,
@@ -12,7 +17,7 @@ import { getDb, schema } from '../db/index.js';
* orphaned DM attachments). The caller is responsible for disk cleanup * orphaned DM attachments). The caller is responsible for disk cleanup
* and WebSocket disconnection after calling this. * and WebSocket disconnection after calling this.
*/ */
export function tombstoneUser(uid: string): string[] { export function tombstoneUser(uid: string, options?: TombstoneOptions): string[] {
const db = getDb(); const db = getDb();
const user = db.select().from(schema.users).where(eq(schema.users.id, uid)).get(); const user = db.select().from(schema.users).where(eq(schema.users.id, uid)).get();
@@ -22,6 +27,8 @@ export function tombstoneUser(uid: string): string[] {
if (user.avatar) filesToDelete.push(user.avatar); if (user.avatar) filesToDelete.push(user.avatar);
if (user.banner) filesToDelete.push(user.banner); if (user.banner) filesToDelete.push(user.banner);
const purge = options?.purgeContent !== false; // default true
// Find group DMs this user owns so we can transfer ownership // Find group DMs this user owns so we can transfer ownership
const ownedGroupDms = db.select({ id: schema.dmChannels.id }) const ownedGroupDms = db.select({ id: schema.dmChannels.id })
.from(schema.dmChannels) .from(schema.dmChannels)
@@ -36,8 +43,10 @@ export function tombstoneUser(uid: string): string[] {
tx.delete(schema.friendRequests).where(or(eq(schema.friendRequests.fromId, uid), eq(schema.friendRequests.toId, uid))).run(); tx.delete(schema.friendRequests).where(or(eq(schema.friendRequests.fromId, uid), eq(schema.friendRequests.toId, uid))).run();
tx.delete(schema.dmMembers).where(eq(schema.dmMembers.userId, uid)).run(); tx.delete(schema.dmMembers).where(eq(schema.dmMembers.userId, uid)).run();
tx.delete(schema.readStates).where(eq(schema.readStates.userId, uid)).run(); tx.delete(schema.readStates).where(eq(schema.readStates.userId, uid)).run();
if (purge) {
tx.delete(schema.reactions).where(eq(schema.reactions.userId, uid)).run(); tx.delete(schema.reactions).where(eq(schema.reactions.userId, uid)).run();
tx.delete(schema.dmReactions).where(eq(schema.dmReactions.userId, uid)).run(); tx.delete(schema.dmReactions).where(eq(schema.dmReactions.userId, uid)).run();
}
tx.delete(schema.spaceFolders).where(eq(schema.spaceFolders.userId, uid)).run(); tx.delete(schema.spaceFolders).where(eq(schema.spaceFolders.userId, uid)).run();
// Conditional deletes for tables that may reference userId // Conditional deletes for tables that may reference userId
@@ -76,6 +85,7 @@ export function tombstoneUser(uid: string): string[] {
} }
} }
if (purge) {
// Clean up orphaned DM channels (zero members after our removal) // Clean up orphaned DM channels (zero members after our removal)
const orphanedDmIds = tx.select({ id: schema.dmChannels.id }) const orphanedDmIds = tx.select({ id: schema.dmChannels.id })
.from(schema.dmChannels) .from(schema.dmChannels)
@@ -109,6 +119,7 @@ export function tombstoneUser(uid: string): string[] {
} }
tx.delete(schema.dmChannels).where(eq(schema.dmChannels.id, dmId)).run(); tx.delete(schema.dmChannels).where(eq(schema.dmChannels.id, dmId)).run();
} }
}
// Tombstone user row — rename username to free it for reuse // Tombstone user row — rename username to free it for reuse
tx.update(schema.users).set({ tx.update(schema.users).set({