From b412455e9d8e5c8cb500c1d96d735a6bfc2d2d50 Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Sun, 15 Jun 2025 21:08:31 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=AE=20feat:=20Enhance=20"Delete=20User?= =?UTF-8?q?"=20Script=20(#7899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 fix: Enhance user deletion script to allow deep deletion of related data * 🔧 fix: Update user deletion script to confirm deep deletion of transaction history * 🔧 fix: Refactor user deletion script to use graceful exit and ensure deep deletion of related data * Update config/delete-user.js is a good idea Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- config/delete-user.js | 124 ++++++++++++++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 29 deletions(-) diff --git a/config/delete-user.js b/config/delete-user.js index 36f34fb21b..51caa7ce3b 100644 --- a/config/delete-user.js +++ b/config/delete-user.js @@ -1,50 +1,116 @@ +#!/usr/bin/env node const path = require('path'); const mongoose = require(path.resolve(__dirname, '..', 'api', 'node_modules', 'mongoose')); -const { User } = require('@librechat/data-schemas').createModels(mongoose); +const { + User, + Agent, + Assistant, + Balance, + Transaction, + ConversationTag, + Conversation, + Message, + File, + Key, + MemoryEntry, + PluginAuth, + Prompt, + PromptGroup, + Preset, + Session, + SharedLink, + ToolCall, + Token, +} = require('@librechat/data-schemas').createModels(mongoose); require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); const { askQuestion, silentExit } = require('./helpers'); const connect = require('./connect'); +async function gracefulExit(code = 0) { + try { + await mongoose.disconnect(); + } catch (err) { + console.error('Error disconnecting from MongoDB:', err); + } + silentExit(code); +} + (async () => { await connect(); - /** - * Show the welcome / help menu - */ console.purple('---------------'); - console.purple('Deleting a user'); + console.purple('Deleting a user and all related data'); console.purple('---------------'); - let email = ''; - if (process.argv.length >= 3) { - email = process.argv[2]; - } else { - email = await askQuestion('Email:'); - } - let user = await User.findOne({ email: email }); - if (user !== null) { - if ((await askQuestion(`Delete user ${user}?`)) === 'y') { - user = await User.findOneAndDelete({ _id: user._id }); - if (user !== null) { - console.yellow(`Deleted user ${user}`); - } else { - console.yellow(`Couldn't delete user with email ${email}`); - } - } - } else { - console.yellow(`Didn't find user with email ${email}`); + // 1) Get email + let email = process.argv[2]?.trim(); + if (!email) { + email = (await askQuestion('Email:')).trim(); } - silentExit(0); -})(); + // 2) Find user + const user = await User.findOne({ email: email.toLowerCase() }); + if (!user) { + console.yellow(`No user found with email "${email}"`); + return gracefulExit(0); + } -process.on('uncaughtException', (err) => { + // 3) Confirm full deletion + const confirmAll = await askQuestion( + `Really delete user ${user.email} (${user._id}) and ALL their data? (y/N)`, + ); + if (confirmAll.toLowerCase() !== 'y') { + console.yellow('Aborted.'); + return gracefulExit(0); + } + + // 4) Ask specifically about transactions + const confirmTx = await askQuestion('Also delete all transaction history for this user? (y/N)'); + const deleteTx = confirmTx.toLowerCase() === 'y'; + + const uid = user._id.toString(); + + // 5) Build and run deletion tasks + const tasks = [ + Agent.deleteMany({ author: uid }), + Assistant.deleteMany({ user: uid }), + Balance.deleteMany({ user: uid }), + ConversationTag.deleteMany({ user: uid }), + Conversation.deleteMany({ user: uid }), + Message.deleteMany({ user: uid }), + File.deleteMany({ user: uid }), + Key.deleteMany({ userId: uid }), + MemoryEntry.deleteMany({ userId: uid }), + PluginAuth.deleteMany({ userId: uid }), + Prompt.deleteMany({ author: uid }), + PromptGroup.deleteMany({ author: uid }), + Preset.deleteMany({ user: uid }), + Session.deleteMany({ user: uid }), + SharedLink.deleteMany({ user: uid }), + ToolCall.deleteMany({ user: uid }), + Token.deleteMany({ userId: uid }), + ]; + + if (deleteTx) { + tasks.push(Transaction.deleteMany({ user: uid })); + } + + await Promise.all(tasks); + + // 6) Finally delete the user document itself + await User.deleteOne({ _id: uid }); + + console.green(`✔ Successfully deleted user ${email} and all associated data.`); + if (!deleteTx) { + console.yellow('⚠️ Transaction history was retained.'); + } + + return gracefulExit(0); +})().catch(async (err) => { if (!err.message.includes('fetch failed')) { console.error('There was an uncaught error:'); console.error(err); - } - - if (!err.message.includes('fetch failed')) { + await mongoose.disconnect(); process.exit(1); } });