From 5ef79ea8cd6b9fd27f75ff473e9de1b200ee189a Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 3 Apr 2026 04:19:58 +0200 Subject: [PATCH] feat: broadcast member_left and user_updated on admin deletion --- packages/server/src/routes/admin.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/server/src/routes/admin.ts b/packages/server/src/routes/admin.ts index 4ed20ba3..8421b700 100644 --- a/packages/server/src/routes/admin.ts +++ b/packages/server/src/routes/admin.ts @@ -5,7 +5,7 @@ import { authenticate, requireAdmin, hashPassword } from '../utils/auth.js'; import { getStorageStats, getOrphanedFiles, cleanupStorage, cleanupOldMedia } from '../utils/storageJanitor.js'; import { getDb, schema } from '../db/index.js'; import { connectionManager } from '../ws/handler.js'; -import { tombstoneUser } from '../utils/userDeletion.js'; +import { tombstoneUser, collectDeletionBroadcastTargets } from '../utils/userDeletion.js'; import { deleteUploadFile } from '../utils/fileCleanup.js'; import { sanitizeUser } from '../utils/sanitize.js'; import type { AdminUser, AdminUserListResponse, AdminResetPasswordResponse } from '@backspace/shared'; @@ -297,11 +297,33 @@ export async function adminRoutes(app: FastifyInstance): Promise { }); } + // Collect broadcast targets BEFORE tombstone deletes DB rows + const { memberSpaceIds, targetUserIds } = collectDeletionBroadcastTargets(targetId); + const filesToDelete = tombstoneUser(targetId); for (const filename of filesToDelete) { deleteUploadFile(filename); } + // Broadcast member_left to each space + for (const spaceId of memberSpaceIds) { + connectionManager.sendToSpace(spaceId, { + type: 'member_left', + spaceId, + userId: targetId, + }); + } + + // Broadcast user_updated with sanitized deleted user data + const deletedRow = getDb().select().from(schema.users).where(eq(schema.users.id, targetId)).get(); + if (deletedRow) { + const deletedUser = sanitizeUser(deletedRow); + const userUpdatedEvent = { type: 'user_updated' as const, user: deletedUser }; + for (const uid of targetUserIds) { + connectionManager.sendToUser(uid, userUpdatedEvent); + } + } + connectionManager.forceDisconnectUser(targetId); return reply.code(200).send({ success: true });